Шаг 3. Реализация сортировки слиянием (Merge Sort) по году
Создайте метод: public void mergeSortByYear()
Реализуйте классический рекурсивный Merge Sort:
Создайте вспомогательный метод mergeSort(List<Book> list, int left, int right)
Если left < right:
mid = (left + right) / 2
Рекурсивно сортируйте левую половину
Рекурсивно сортируйте правую половину
Слейте две отсортированные половины в методе merge()
Реализуйте метод merge(List<Book> list, int left, int mid, int right):
Создайте временный список temp нужного размера
Два указателя i = left, j = mid+1
Пока обе половины не закончились — сравнивайте и копируйте меньший элемент
Докопируйте остаток левой или правой половины
Скопируйте temp обратно в исходный список на позиции left..right
Ключевой момент устойчивости:
Merge Sort устойчив — при равных ключах сохраняется относительный порядок исходных элементов.
После сортировки вызовите printAllBooks() и убедитесь, что книги Достоевского 1869 года остались в исходном порядке (Преступление → Идиот).
Шаг 4. Использование встроенной сортировки (Collections.sort и List.sort)
Создайте метод sortWithCollections():
Вызовите Collections.sort(books) — поскольку Book реализует Comparable по названию (из предыдущего урока), отсортирует по названию
Затем вызовите books.sort(Comparator.comparingInt(Book::getYear)) — сортировка по году
Затем вызовите books.sort(Comparator.comparing(Book::getAuthor).thenComparingInt(Book::getYear)) — по автору, затем по году
После каждой сортировки выводите список и наблюдайте порядок.
Исследование Timsort:
Начиная с Java 7, Collections.sort() и List.sort() используют Timsort — гибридный алгоритм:
Сортировка вставками для малых подмассивов (run ≤ 32)
Слияние (merge) для больших
Очень эффективен на реальных данных (частично отсортированных)
Устойчив — сохраняет относительный порядок равных элементов
Анализ: Когда важна устойчивость сортировки?
Устойчивость (stable sort) — когда при равных ключах сохраняется исходный относительный порядок элементов.
В библиотечном приложении устойчивость важна в следующих случаях:
Сортировка по году, но хочется сохранить алфавитный порядок авторов при одинаковом годе
→ Если два произведения Достоевского вышли в 1869 году — после сортировки по году они должны остаться в порядке появления в каталоге или по алфавиту названий.
Сортировка по рейтингу, но при равном рейтинге важен порядок добавления
→ Пользователь добавил книги в определённом порядке — при равном рейтинге хочется видеть их в этом же порядке.
Многоуровневая сортировка
→ Сначала по автору, затем по году — устойчивость гарантирует, что внутри одного автора книги останутся в порядке добавления или по названию.
Когда устойчивость не критична:
Сортировка по уникальному полю (ISBN, ID) — равных элементов нет
Когда порядок внутри группы не имеет значения (например, только по жанру)
Когда важна максимальная скорость и экономия памяти, а не порядок
Сравнение по памяти и устойчивости:
Прогноз при росте данных:
100 книг → любой алгоритм за миллисекунды
100 000 книг → Selection Sort — секунды, остальные — десятки миллисекунд
1 000 000 книг → Selection Sort — часы, Timsort/Merge — секунды, HeapSort — чуть быстрее MergeSort за счёт меньшей памяти
Вывод для библиотеки:
Основной выбор — List.sort() (Timsort): устойчивый, быстрый, адаптивный
Merge Sort вручную — только если нужно изучить алгоритм или требуется явный контроль
HeapSort — если память критична (очень редкий случай)
Selection Sort — исключительно для обучения
Практическое задание (объёмное)
Реализуйте selectionSortByYear() вручную.
Реализуйте рекурсивный mergeSortByYear() с вспомогательным merge.
Добавьте методы:
sortByTitleUsingCollections()
sortByAuthorThenYearUsingComparator()
sortByYearUsingLambda()
В main добавьте 10+ книг с дубликатами годов и авторов.
Выполните все сортировки подряд и выводите список после каждой.
Сравните порядок книг Достоевского 1869 года до и после сортировки по году — убедитесь в устойчивости Timsort и MergeSort.
#Java #для_новичков #beginner #algorithm #sorted #HeapSort #Практика
Создайте метод: public void mergeSortByYear()
Реализуйте классический рекурсивный Merge Sort:
Создайте вспомогательный метод mergeSort(List<Book> list, int left, int right)
Если left < right:
mid = (left + right) / 2
Рекурсивно сортируйте левую половину
Рекурсивно сортируйте правую половину
Слейте две отсортированные половины в методе merge()
Реализуйте метод merge(List<Book> list, int left, int mid, int right):
Создайте временный список temp нужного размера
Два указателя i = left, j = mid+1
Пока обе половины не закончились — сравнивайте и копируйте меньший элемент
Докопируйте остаток левой или правой половины
Скопируйте temp обратно в исходный список на позиции left..right
Ключевой момент устойчивости:
Merge Sort устойчив — при равных ключах сохраняется относительный порядок исходных элементов.
После сортировки вызовите printAllBooks() и убедитесь, что книги Достоевского 1869 года остались в исходном порядке (Преступление → Идиот).
Шаг 4. Использование встроенной сортировки (Collections.sort и List.sort)
Создайте метод sortWithCollections():
Вызовите Collections.sort(books) — поскольку Book реализует Comparable по названию (из предыдущего урока), отсортирует по названию
Затем вызовите books.sort(Comparator.comparingInt(Book::getYear)) — сортировка по году
Затем вызовите books.sort(Comparator.comparing(Book::getAuthor).thenComparingInt(Book::getYear)) — по автору, затем по году
После каждой сортировки выводите список и наблюдайте порядок.
Исследование Timsort:
Начиная с Java 7, Collections.sort() и List.sort() используют Timsort — гибридный алгоритм:
Сортировка вставками для малых подмассивов (run ≤ 32)
Слияние (merge) для больших
Очень эффективен на реальных данных (частично отсортированных)
Устойчив — сохраняет относительный порядок равных элементов
Анализ: Когда важна устойчивость сортировки?
Устойчивость (stable sort) — когда при равных ключах сохраняется исходный относительный порядок элементов.
В библиотечном приложении устойчивость важна в следующих случаях:
Сортировка по году, но хочется сохранить алфавитный порядок авторов при одинаковом годе
→ Если два произведения Достоевского вышли в 1869 году — после сортировки по году они должны остаться в порядке появления в каталоге или по алфавиту названий.
Сортировка по рейтингу, но при равном рейтинге важен порядок добавления
→ Пользователь добавил книги в определённом порядке — при равном рейтинге хочется видеть их в этом же порядке.
Многоуровневая сортировка
→ Сначала по автору, затем по году — устойчивость гарантирует, что внутри одного автора книги останутся в порядке добавления или по названию.
Когда устойчивость не критична:
Сортировка по уникальному полю (ISBN, ID) — равных элементов нет
Когда порядок внутри группы не имеет значения (например, только по жанру)
Когда важна максимальная скорость и экономия памяти, а не порядок
Сравнение по памяти и устойчивости:
Прогноз при росте данных:
100 книг → любой алгоритм за миллисекунды
100 000 книг → Selection Sort — секунды, остальные — десятки миллисекунд
1 000 000 книг → Selection Sort — часы, Timsort/Merge — секунды, HeapSort — чуть быстрее MergeSort за счёт меньшей памяти
Вывод для библиотеки:
Основной выбор — List.sort() (Timsort): устойчивый, быстрый, адаптивный
Merge Sort вручную — только если нужно изучить алгоритм или требуется явный контроль
HeapSort — если память критична (очень редкий случай)
Selection Sort — исключительно для обучения
Практическое задание (объёмное)
Реализуйте selectionSortByYear() вручную.
Реализуйте рекурсивный mergeSortByYear() с вспомогательным merge.
Добавьте методы:
sortByTitleUsingCollections()
sortByAuthorThenYearUsingComparator()
sortByYearUsingLambda()
В main добавьте 10+ книг с дубликатами годов и авторов.
Выполните все сортировки подряд и выводите список после каждой.
Сравните порядок книг Достоевского 1869 года до и после сортировки по году — убедитесь в устойчивости Timsort и MergeSort.
#Java #для_новичков #beginner #algorithm #sorted #HeapSort #Практика
👍1
Что выведет код?
#Tasks
import java.util.Random;
public class Task220126 {
public static void main(String[] args) {
long seed = System.currentTimeMillis();
Random r1 = new Random(seed);
Random r2 = new Random(seed);
int a = r1.nextInt();
int b = r2.nextInt();
System.out.println(a == b);
}
}
#Tasks
👍1
👍1
Почему HashMap не потокобезопасен и что конкретно может пойти не так? 🤓
Ответ:
HashMap не потокобезопасен, потому что при одновремной записи структура внутренней таблицы может быть повреждена.
В старых версиях Java это могло привести даже к бесконечному циклу при resize. Основная проблема — отсутствие синхронизации при изменении бакетов и связей между ними. Даже если один поток пишет, а другой читает, возможна неконсистентность данных. Использование synchronizedMap или ConcurrentHashMap решает проблему, но с разной ценой по производительности.
Важно понимать, что проблема не только в «неправильных данных», а в потенциальном нарушении структуры коллекции.
#собеседование
Ответ:
В старых версиях Java это могло привести даже к бесконечному циклу при resize. Основная проблема — отсутствие синхронизации при изменении бакетов и связей между ними. Даже если один поток пишет, а другой читает, возможна неконсистентность данных. Использование synchronizedMap или ConcurrentHashMap решает проблему, но с разной ценой по производительности.
Важно понимать, что проблема не только в «неправильных данных», а в потенциальном нарушении структуры коллекции.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Please open Telegram to view this post
VIEW IN TELEGRAM
👾2
История IT-технологий сегодня — 23 января
ℹ️ Кто родился в этот день
Дави́д Ги́льберт (нем. David Hilbert; 23 января 1862 — 14 февраля 1943) — один из крупнейших математиков XX века; его формализация геометрии, теория интегральных уравнений и концепция пространств Гильберта легли в основу функционального анализа, квантовой теории и многих численных методов, используемых в алгоритмах, машинном обучении и обработке сигналов.
🌐 Знаковые события
1996 — релиз Java 1.0: первая официальная версия языка Java, ориентированная на модель «write once, run anywhere» для интернет‑приложений; язык стал основой для серверной разработки, Android, корпоративных систем и огромной части современного ПО.
1998 – Netscape анонсирует Mozilla с намерением выпустить код Communicator в качестве открытого исходного кода .
#Biography #Birth_Date #Events #23Января
Дави́д Ги́льберт (нем. David Hilbert; 23 января 1862 — 14 февраля 1943) — один из крупнейших математиков XX века; его формализация геометрии, теория интегральных уравнений и концепция пространств Гильберта легли в основу функционального анализа, квантовой теории и многих численных методов, используемых в алгоритмах, машинном обучении и обработке сигналов.
1996 — релиз Java 1.0: первая официальная версия языка Java, ориентированная на модель «write once, run anywhere» для интернет‑приложений; язык стал основой для серверной разработки, Android, корпоративных систем и огромной части современного ПО.
1998 – Netscape анонсирует Mozilla с намерением выпустить код Communicator в качестве открытого исходного кода .
#Biography #Birth_Date #Events #23Января
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Глава 4: Эффективный поиск. Бинарный и не только
Бинарный поиск — принцип и реализация
Представьте, что вы ищете нужную книгу на полке, где издания разложены в алфавитном порядке. Интуитивно вы не будете проверять каждую книгу подряд — вы откроете середину, поймете, слишком рано или поздно в алфавите упустили нужную, и за пару шагов сузите область поиска до нескольких экземпляров. Бинарный поиск — это формализация этой интуиции в алгоритмической форме, где каждая итерация делит область поиска ровно пополам.
Метод применим не только к книгам. Любая структура данных, допускающая индексный доступ (мгновенное обращение к элементу по его порядковому номеру) и поддерживающая отношение полного порядка (возможность сравнить два элемента и сказать, какой «меньше»), может стать кандидатом для бинарного поиска. Массивы, ArrayList, сегменты файлов на диске — типичные носители.
Аксиома: данные должны быть отсортированы
Прежде чем разбирать сам алгоритм, зафиксируем ключевое требование: исходная коллекция должна быть упорядочена относительно ключа поиска. Это не формальность, а математическая необходимость.
Представьте отсортированный массив как монотонную функцию f(i) = array[i], где при увеличении индекса i значение не убывает. Это свойство монотонности гарантирует, что если array[mid] > target, то искомый элемент (если он вообще присутствует) не может находиться правее позиции mid. Аналогично для случая array[mid] < target. Это утверждение выполняется только в отсортированной последовательности.
Когда мы говорим о сортировке в контексте бинарного поиска, имеем в виду строгий порядок, поддерживаемый на всем диапазоне от array[0] до array[n-1]. Любое нарушение этого правила — и алгоритм начнет отбрасывать правильные ответы вместе с «плохими» половинами, возвращая ложные отрицательные результаты.
Ядро алгоритма: принцип деления пополам
Бинарный поиск работает по схеме «разделяй и властвуй», но в отличие от сортировки слиянием здесь мы не комбинируем результаты подзадач.
Алгоритм состоит из циклически повторяющихся шагов:
Инициализация: Задаем два указателя — left, охватывающий начало рабочей области (обычно индекс 0), и right, обозначающий конец (последний допустимый индекс или размер массива в зависимости от вариации реализации).
Итерация: Пока область поиска не схлопнулась (left <= right или эквивалентное условие):
Вычисляем середину: mid = left + (right - left) / 2. Эта формула защищает от целочисленного переполнения, которое могло бы случиться при более прямолинейном (left + right) / 2 на очень больших массивах.
Сравниваем array[mid] с целевым значением target.
Если array[mid] == target — поиск успешен, возвращаем mid.
Если array[mid] < target — искомый элемент, если существует, находится строго правее. Сдвигаем left = mid + 1.
Иначе (array[mid] > target) — сдвигаем right = mid - 1.
Завершение: Если выход из цикла произошел без нахождения элемента, возвращаем индикатор отсутствия (обычно -1 или Optional.empty()).
Важный момент: способ вычисления mid и обновления границ формирует два семейства реализаций — с включенной правой границей (right указывает на последний допустимый индекс) и с исключенной (right указывает на первый индекс за пределами области). Оба подхода рабочие, но условия цикла и обновления границ меняются зеркально. Мы разберем вариант с включенной границей, как более интуитивный для начального понимания.
#Java #для_новичков #beginner #algorithm #sorted #binary
Бинарный поиск — принцип и реализация
Представьте, что вы ищете нужную книгу на полке, где издания разложены в алфавитном порядке. Интуитивно вы не будете проверять каждую книгу подряд — вы откроете середину, поймете, слишком рано или поздно в алфавите упустили нужную, и за пару шагов сузите область поиска до нескольких экземпляров. Бинарный поиск — это формализация этой интуиции в алгоритмической форме, где каждая итерация делит область поиска ровно пополам.
Метод применим не только к книгам. Любая структура данных, допускающая индексный доступ (мгновенное обращение к элементу по его порядковому номеру) и поддерживающая отношение полного порядка (возможность сравнить два элемента и сказать, какой «меньше»), может стать кандидатом для бинарного поиска. Массивы, ArrayList, сегменты файлов на диске — типичные носители.
Аксиома: данные должны быть отсортированы
Прежде чем разбирать сам алгоритм, зафиксируем ключевое требование: исходная коллекция должна быть упорядочена относительно ключа поиска. Это не формальность, а математическая необходимость.
Представьте отсортированный массив как монотонную функцию f(i) = array[i], где при увеличении индекса i значение не убывает. Это свойство монотонности гарантирует, что если array[mid] > target, то искомый элемент (если он вообще присутствует) не может находиться правее позиции mid. Аналогично для случая array[mid] < target. Это утверждение выполняется только в отсортированной последовательности.
Когда мы говорим о сортировке в контексте бинарного поиска, имеем в виду строгий порядок, поддерживаемый на всем диапазоне от array[0] до array[n-1]. Любое нарушение этого правила — и алгоритм начнет отбрасывать правильные ответы вместе с «плохими» половинами, возвращая ложные отрицательные результаты.
Ядро алгоритма: принцип деления пополам
Бинарный поиск работает по схеме «разделяй и властвуй», но в отличие от сортировки слиянием здесь мы не комбинируем результаты подзадач.
Алгоритм состоит из циклически повторяющихся шагов:
Инициализация: Задаем два указателя — left, охватывающий начало рабочей области (обычно индекс 0), и right, обозначающий конец (последний допустимый индекс или размер массива в зависимости от вариации реализации).
Итерация: Пока область поиска не схлопнулась (left <= right или эквивалентное условие):
Вычисляем середину: mid = left + (right - left) / 2. Эта формула защищает от целочисленного переполнения, которое могло бы случиться при более прямолинейном (left + right) / 2 на очень больших массивах.
Сравниваем array[mid] с целевым значением target.
Если array[mid] == target — поиск успешен, возвращаем mid.
Если array[mid] < target — искомый элемент, если существует, находится строго правее. Сдвигаем left = mid + 1.
Иначе (array[mid] > target) — сдвигаем right = mid - 1.
Завершение: Если выход из цикла произошел без нахождения элемента, возвращаем индикатор отсутствия (обычно -1 или Optional.empty()).
Важный момент: способ вычисления mid и обновления границ формирует два семейства реализаций — с включенной правой границей (right указывает на последний допустимый индекс) и с исключенной (right указывает на первый индекс за пределами области). Оба подхода рабочие, но условия цикла и обновления границ меняются зеркально. Мы разберем вариант с включенной границей, как более интуитивный для начального понимания.
#Java #для_новичков #beginner #algorithm #sorted #binary
👍4
Итеративная реализация: фундамент
Итеративная версия — самая эффективная с точки зрения потребления памяти. Она использует фиксированное количество переменных на стеке и не порождает дополнительных вызовов функций.
Ключевые детали реализации:
Переполнение безопасность: Формула left + (right - left) / 2 критична на массивах длиной более Integer.MAX_VALUE / 2. Хотя в Java массив не может иметь такой размер из-за ограничений JVM и индексов типа int, эта привычка переносится в языки с беззнаковыми индексами (C++, Rust) и показывает профессиональный уровень кодирования.
Инвариант цикла: Перед каждой итерацией выполняется условие — все элементы левее left гарантированно меньше target, все элементы правее right гарантированно больше target. Сохранение этого инварианта — залог корректности.
Точка выхода: Условие left <= right означает, что даже когда left == right, у нас остался один кандидат на проверку. Если его проверка не дает совпадения, на следующей итерации left станет больше right и цикл завершится.
#Java #для_новичков #beginner #algorithm #sorted #binary
Итеративная версия — самая эффективная с точки зрения потребления памяти. Она использует фиксированное количество переменных на стеке и не порождает дополнительных вызовов функций.
public class BinarySearch {
/**
* Выполняет бинарный поиск целевого значения в отсортированном массиве.
*
* @param array отсортированный массив целых чисел (по возрастанию)
* @param target искомое значение
* @return индекс target в array или -1, если элемент не найден
* @throws IllegalArgumentException если array равен null
*/
public static int search(int[] array, int target) {
if (array == null) {
throw new IllegalArgumentException("Array cannot be null");
}
// left и right задают замкнутый интервал [left, right], в котором может находиться target
int left = 0;
int right = array.length - 1;
// Пока интервал не схлопнулся (не стал пустым)
while (left <= right) {
// Вычисляем середину, защищаясь от переполнения
// Эквивалентно (left + right) / 2, но без риска overflow
int mid = left + (right - left) / 2;
// Получаем значение по среднему индексу
int midValue = array[mid];
// Сравниваем с целевым
if (midValue == target) {
// Нашли! Возвращаем позицию.
return mid;
} else if (midValue < target) {
// Искомое значение больше, чем midValue.
// Отбрасываем левую половину, включая mid.
// Новый поиск в (mid + 1, right).
left = mid + 1;
} else { // midValue > target
// Искомое значение меньше, чем midValue.
// Отбрасываем правую половину, включая mid.
// Новый поиск в (left, mid - 1).
right = mid - 1;
}
}
// Цикл завершился, left > right. Это значит, что интервал поиска пуст.
// Следовательно, target в массиве отсутствует.
return -1;
}
}Ключевые детали реализации:
Переполнение безопасность: Формула left + (right - left) / 2 критична на массивах длиной более Integer.MAX_VALUE / 2. Хотя в Java массив не может иметь такой размер из-за ограничений JVM и индексов типа int, эта привычка переносится в языки с беззнаковыми индексами (C++, Rust) и показывает профессиональный уровень кодирования.
Инвариант цикла: Перед каждой итерацией выполняется условие — все элементы левее left гарантированно меньше target, все элементы правее right гарантированно больше target. Сохранение этого инварианта — залог корректности.
Точка выхода: Условие left <= right означает, что даже когда left == right, у нас остался один кандидат на проверку. Если его проверка не дает совпадения, на следующей итерации left станет больше right и цикл завершится.
#Java #для_новичков #beginner #algorithm #sorted #binary
👍4
Рекурсивная реализация: элегантность и цена
Рекурсия выражает бинарный поиск более декларативно: «Поиск в массиве — это поиск в середине, иначе поиск в подмассиве». Код становится лаконичнее, но мы платим за это вызовами функций и расходом стековой памяти.
Анализ рекурсивной версии:
Чистота: Нет изменяемых переменных в цикле. Алгоритм читается как математическая рекуррентная формула.
Стоимость вызовов: Каждый рекурсивный шаг добавляет фрейм в стек вызовов. Фрейм содержит локальные переменные mid, midValue и параметры left, right. Глубина рекурсии составляет ⌈log₂n⌉ + 1 (включая первый вызов). На массиве из миллиона элементов глубина не превысит 20 уровней. Звучит безопасно, но...
Проблема переполнения стека: Стандартный размер стека в Java потоке — 1 МБ. 20 фреймов занимают крошечную часть. Однако в средах с ограниченным стеком (встроенные системы, агрессивные настройки JVM -Xss) или при поиске в коллекциях, эмулируемых через рекурсивные структуры, риск существует. Теоретически, на массиве размером 2³⁰ (~1 миллиард) элементов глубина достигла бы 31, что все еще безопасно, но практические ограничения JVM на размер массива не позволят такой эксперимент.
Оптимизация хвостовой рекурсии: Java не гарантирует оптимизацию хвостовой рекурсии (TCE), в отличие от языков функциональной парадигмы (Scala, Haskell). Каждый вызов будет реализован полностью. Поэтому итеративная версия всегда предпочтительнее с точки зрения производительности.
#Java #для_новичков #beginner #algorithm #sorted #binary
Рекурсия выражает бинарный поиск более декларативно: «Поиск в массиве — это поиск в середине, иначе поиск в подмассиве». Код становится лаконичнее, но мы платим за это вызовами функций и расходом стековой памяти.
public class BinarySearchRecursive {
/**
* Публичный метод-обертка, скрывающий детали рекурсивной реализации.
*
* @param array отсортированный массив целых чисел
* @param target искомое значение
* @return индекс target или -1
*/
public static int search(int[] array, int target) {
if (array == null) {
throw new IllegalArgumentException("Array cannot be null");
}
// Запускаем рекурсию с полным диапазоном
return searchRecursive(array, target, 0, array.length - 1);
}
/**
* Приватный рекурсивный вспомогательный метод.
*
* @param array исходный массив (не меняется)
* @param target искомое значение
* @param left левая граница поиска (включительно)
* @param right правая граница поиска (включительно)
* @return индекс target или -1
*/
private static int searchRecursive(int[] array, int target, int left, int right) {
// Базовый случай: интервал поиска пуст
// Это аналог выхода из цикла while (left <= right)
if (left > right) {
return -1;
}
// Вычисляем середину
int mid = left + (right - left) / 2;
int midValue = array[mid];
// Базовый случай: нашли элемент
if (midValue == target) {
return mid;
}
// Рекурсивный шаг: выбираем половину и вызываем себя
if (midValue < target) {
// Искомое значение больше — ищем в правой половине
return searchRecursive(array, target, mid + 1, right);
} else {
// Искомое значение меньше — ищем в левой половине
return searchRecursive(array, target, left, mid - 1);
}
}
}Анализ рекурсивной версии:
Чистота: Нет изменяемых переменных в цикле. Алгоритм читается как математическая рекуррентная формула.
Стоимость вызовов: Каждый рекурсивный шаг добавляет фрейм в стек вызовов. Фрейм содержит локальные переменные mid, midValue и параметры left, right. Глубина рекурсии составляет ⌈log₂n⌉ + 1 (включая первый вызов). На массиве из миллиона элементов глубина не превысит 20 уровней. Звучит безопасно, но...
Проблема переполнения стека: Стандартный размер стека в Java потоке — 1 МБ. 20 фреймов занимают крошечную часть. Однако в средах с ограниченным стеком (встроенные системы, агрессивные настройки JVM -Xss) или при поиске в коллекциях, эмулируемых через рекурсивные структуры, риск существует. Теоретически, на массиве размером 2³⁰ (~1 миллиард) элементов глубина достигла бы 31, что все еще безопасно, но практические ограничения JVM на размер массива не позволят такой эксперимент.
Оптимизация хвостовой рекурсии: Java не гарантирует оптимизацию хвостовой рекурсии (TCE), в отличие от языков функциональной парадигмы (Scala, Haskell). Каждый вызов будет реализован полностью. Поэтому итеративная версия всегда предпочтительнее с точки зрения производительности.
#Java #для_новичков #beginner #algorithm #sorted #binary
👍3
Асимптотический анализ: время и память
Временная сложность: O(log n)
Логарифмическая сложность означает, что с каждой итерацией мы отбрасываем половину оставшихся кандидатов. Формально: пусть T(n) — время поиска в массиве размера n. Тогда T(n) = T(n/2) + O(1), где O(1) — это сравнение и вычисление mid. Решая это рекуррентное соотношение по методу мастера, получаем T(n) = Θ(log n). В худшем случае нам потребуется ⌊log₂n⌋ + 1 сравнение.
Для системы масштабирования: увеличение коллекции в 1024 раз (2¹⁰) добавит только 10 итераций. Это волшебство логарифма: поиск в миллионе записей требует максимум 20 шагов, в миллиарде — 30.
Пространственная сложность:
Итеративная версия: O(1) дополнительной памяти. Используем фиксированный набор переменных (left, right, mid, midValue), не зависящий от размера входных данных. Масштабирование идеальное.
Рекурсивная версия: O(log n) из-за стековых фреймов. Каждый вызов создает фрейм (~24-32 байта на локальные переменные, указатель на return address и т.д.). При глубине log n общий объем стека линейно зависит от log n. На практике это означает: если ваша JVM позволяет глубину стека 1000 вызовов, вы безопасно ищете в массивах до 2¹⁰⁰⁰ элементов (число с 300 знаками), что в цифровом виде превышает количество атомов во Вселенной.
Границы применимости: когнитивный диссонанс производительности
Бинарный поиск — не серебряная пуля. Есть контексты, где он не только не выигрывает, но и проигрывает линейному поиску.
1. При малых n (n < 50-100)
Константы в алгоритмах имеют значение. Бинарный поиск требует деления, сложения, ветвления, что накладывает фиксированный налог в несколько процессорных циклов. Линейный поиск для n = 10 может выполниться за 10 простых сравнений, тогда как бинарный — за 4-5 сложных операций. Разница в наносекундах настолько мала, что не компенсирует накладные расходы на предварительную сортировку (если она не была сделана заранее).
Точка окупаемости зависит от архитектуры процессора, языка и стоимости операции сравнения. Для примитивов в Java порог может лежать в диапазоне 50-100 элементов. Для объектов, где сравнение требует дорогого Comparator или метода compareTo, порог смещается влево.
2. При динамических данных с частой вставкой
Если ваша библиотека пополняется новыми книгами каждые 10 минут, а поиск пользователей идет каждую секунду, поддержание отсортированности становится болью. Вставка в отсортированный массив стоит O(n) (сдвиг всех правых элементов). Для n = 10⁶ каждая вставка — это миллион операций копирования. Даже если вы используете сбалансированные структуры вроде TreeSet или PriorityQueue, где вставка O(log n), константы выше, чем в HashSet, и скорость поиска по индексу теряется.
В таких сценариях часто используют гибридный подход: данные хранятся в неотсортированном виде, периодически (например, раз в час) накопленное за период сортируется и сливается с основным отсортированным массивом методом сортировки слиянием. Или используются блочные структуры (bucketed arrays), где каждый блок отсортирован внутри, а новые элементы собираются в буфере.
3. Когда стоимость сравнения доминирует
В бинарном поиске мы всегда делаем log n сравнений. В линейном — в среднем n/2. Если n = 1000, бинарный поиск сделает ~10 сравнений, линейный — 500. Но что если каждое сравнение — это взятие блока данных с диска или сеть RPC? Тогда log n vs n/2 не имеет значения: оба варианта требуют одного дискового доступа (если данные кешированы), и разница в 490 сравнений — просто CPU-циклы в памяти. Бинарный поиск выигрывает в вычислительной сложности, но не в I/O.
4. Когда данные не помещаются в память, но отсортированы
Если массив лежит на диске и доступ к нему происходит через постраничную загрузку (paging), бинарный поиск становится врагом локальности. Итерация 0 читает страницу с середины. Итерация 1 читает страницу с четвертью или тремя четвертями — совершенно другая страница. Для дисковых массивов может быть выгоднее использовать B-деревья или интерполяционный поиск, учитывающий вероятность расположения данных на носителе.
#Java #для_новичков #beginner #algorithm #sorted #binary
Временная сложность: O(log n)
Логарифмическая сложность означает, что с каждой итерацией мы отбрасываем половину оставшихся кандидатов. Формально: пусть T(n) — время поиска в массиве размера n. Тогда T(n) = T(n/2) + O(1), где O(1) — это сравнение и вычисление mid. Решая это рекуррентное соотношение по методу мастера, получаем T(n) = Θ(log n). В худшем случае нам потребуется ⌊log₂n⌋ + 1 сравнение.
Для системы масштабирования: увеличение коллекции в 1024 раз (2¹⁰) добавит только 10 итераций. Это волшебство логарифма: поиск в миллионе записей требует максимум 20 шагов, в миллиарде — 30.
Пространственная сложность:
Итеративная версия: O(1) дополнительной памяти. Используем фиксированный набор переменных (left, right, mid, midValue), не зависящий от размера входных данных. Масштабирование идеальное.
Рекурсивная версия: O(log n) из-за стековых фреймов. Каждый вызов создает фрейм (~24-32 байта на локальные переменные, указатель на return address и т.д.). При глубине log n общий объем стека линейно зависит от log n. На практике это означает: если ваша JVM позволяет глубину стека 1000 вызовов, вы безопасно ищете в массивах до 2¹⁰⁰⁰ элементов (число с 300 знаками), что в цифровом виде превышает количество атомов во Вселенной.
Границы применимости: когнитивный диссонанс производительности
Бинарный поиск — не серебряная пуля. Есть контексты, где он не только не выигрывает, но и проигрывает линейному поиску.
1. При малых n (n < 50-100)
Константы в алгоритмах имеют значение. Бинарный поиск требует деления, сложения, ветвления, что накладывает фиксированный налог в несколько процессорных циклов. Линейный поиск для n = 10 может выполниться за 10 простых сравнений, тогда как бинарный — за 4-5 сложных операций. Разница в наносекундах настолько мала, что не компенсирует накладные расходы на предварительную сортировку (если она не была сделана заранее).
Точка окупаемости зависит от архитектуры процессора, языка и стоимости операции сравнения. Для примитивов в Java порог может лежать в диапазоне 50-100 элементов. Для объектов, где сравнение требует дорогого Comparator или метода compareTo, порог смещается влево.
2. При динамических данных с частой вставкой
Если ваша библиотека пополняется новыми книгами каждые 10 минут, а поиск пользователей идет каждую секунду, поддержание отсортированности становится болью. Вставка в отсортированный массив стоит O(n) (сдвиг всех правых элементов). Для n = 10⁶ каждая вставка — это миллион операций копирования. Даже если вы используете сбалансированные структуры вроде TreeSet или PriorityQueue, где вставка O(log n), константы выше, чем в HashSet, и скорость поиска по индексу теряется.
В таких сценариях часто используют гибридный подход: данные хранятся в неотсортированном виде, периодически (например, раз в час) накопленное за период сортируется и сливается с основным отсортированным массивом методом сортировки слиянием. Или используются блочные структуры (bucketed arrays), где каждый блок отсортирован внутри, а новые элементы собираются в буфере.
3. Когда стоимость сравнения доминирует
В бинарном поиске мы всегда делаем log n сравнений. В линейном — в среднем n/2. Если n = 1000, бинарный поиск сделает ~10 сравнений, линейный — 500. Но что если каждое сравнение — это взятие блока данных с диска или сеть RPC? Тогда log n vs n/2 не имеет значения: оба варианта требуют одного дискового доступа (если данные кешированы), и разница в 490 сравнений — просто CPU-циклы в памяти. Бинарный поиск выигрывает в вычислительной сложности, но не в I/O.
4. Когда данные не помещаются в память, но отсортированы
Если массив лежит на диске и доступ к нему происходит через постраничную загрузку (paging), бинарный поиск становится врагом локальности. Итерация 0 читает страницу с середины. Итерация 1 читает страницу с четвертью или тремя четвертями — совершенно другая страница. Для дисковых массивов может быть выгоднее использовать B-деревья или интерполяционный поиск, учитывающий вероятность расположения данных на носителе.
#Java #для_новичков #beginner #algorithm #sorted #binary
👍3🔥1
Что выведет код?
#Tasks
import java.util.*;
public class Task230126 {
public static void main(String[] args) {
List<String> list = Arrays.asList("apple", "banana", "grape", "orange");
int index1 = Collections.binarySearch(list, "grape");
int index2 = Collections.binarySearch(list, "kiwi");
System.out.println("Grape: " + index1);
System.out.println("Kiwi: " + index2);
}
}
#Tasks
Варианты ответа:
Anonymous Quiz
6%
Grape: 2, Kiwi: -2
18%
Grape: 2, Kiwi: -3
12%
Grape: 2, Kiwi: -4
65%
Exception
Почему equals() без переопределения hashCode() — это баг? 🤓
Ответ:
Контракт Java требует: если equals() возвращает true, hashCode() обязан быть одинаковым.
Если переопределить только equals, HashMap и HashSet перестают работать корректно — объект может быть помещён в одну корзину, а искаться в другой. Это приводит к «потерянным» элементам, которые физически есть в коллекции, но логически недоступны. Ошибка сложно диагностируется, так как код компилируется и частично работает.
Это не вопрос стиля — это нарушение фундаментального контракта коллекций.
#собеседование
Ответ:
Если переопределить только equals, HashMap и HashSet перестают работать корректно — объект может быть помещён в одну корзину, а искаться в другой. Это приводит к «потерянным» элементам, которые физически есть в коллекции, но логически недоступны. Ошибка сложно диагностируется, так как код компилируется и частично работает.
Это не вопрос стиля — это нарушение фундаментального контракта коллекций.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История IT-технологий сегодня — 24 января
ℹ️ Кто родился в этот день
Джон фон Не́йман (англ. John von Neumann /vɒn ˈnɔɪmən/; или Иоганн фон Нейман, нем. Johann von Neumann; при рождении Я́нош Ла́йош Нейман, венг. Neumann János Lajos, IPA: [nojmɒn ˈjaːnoʃ ˈlɒjoʃ]; 28 декабря 1903, Будапешт — 8 февраля 1957, Вашингтон) — венгеро-американский математик, физик и педагог еврейского происхождения, сделавший важный вклад в квантовую физику, квантовую логику, функциональный анализ, теорию множеств, информатику, экономику и другие отрасли науки. Наиболее известен как человек, с именем которого связывают архитектуру большинства современных компьютеров (так называемая архитектура фон Неймана), применение теории операторов к квантовой механике (алгебра фон Неймана), а также как участник Манхэттенского проекта и как создатель теории игр и концепции клеточных автоматов.
Ма́рвин Ли Ми́нский (англ. Marvin Lee Minsky; 9 августа 1927 — 24 января 2016) — американский учёный в области искусственного интеллекта, сооснователь Лаборатории искусственного интеллекта в Массачусетском технологическом институте.
🌐 Знаковые события
1984 — старт продаж Apple Macintosh 128K: первый массово успешный персональный компьютер с графическим интерфейсом и мышью; именно Mac популяризировал GUI и сильно повлиял на дизайн операционных систем, настольную полиграфию и массовое восприятие персональных компьютеров
#Biography #Birth_Date #Events #24Января
Джон фон Не́йман (англ. John von Neumann /vɒn ˈnɔɪmən/; или Иоганн фон Нейман, нем. Johann von Neumann; при рождении Я́нош Ла́йош Нейман, венг. Neumann János Lajos, IPA: [nojmɒn ˈjaːnoʃ ˈlɒjoʃ]; 28 декабря 1903, Будапешт — 8 февраля 1957, Вашингтон) — венгеро-американский математик, физик и педагог еврейского происхождения, сделавший важный вклад в квантовую физику, квантовую логику, функциональный анализ, теорию множеств, информатику, экономику и другие отрасли науки. Наиболее известен как человек, с именем которого связывают архитектуру большинства современных компьютеров (так называемая архитектура фон Неймана), применение теории операторов к квантовой механике (алгебра фон Неймана), а также как участник Манхэттенского проекта и как создатель теории игр и концепции клеточных автоматов.
Ма́рвин Ли Ми́нский (англ. Marvin Lee Minsky; 9 августа 1927 — 24 января 2016) — американский учёный в области искусственного интеллекта, сооснователь Лаборатории искусственного интеллекта в Массачусетском технологическом институте.
1984 — старт продаж Apple Macintosh 128K: первый массово успешный персональный компьютер с графическим интерфейсом и мышью; именно Mac популяризировал GUI и сильно повлиял на дизайн операционных систем, настольную полиграфию и массовое восприятие персональных компьютеров
#Biography #Birth_Date #Events #24Января
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
С 17.12 по 23.01
Предыдущий пост(с 10.01 по 16.01)
Воскресный мотивационный пост:
не было мотивации
Запись встреч/видео:
Миграции с Flyway и LiquiBase. Основы управления БД
Обучающие статьи:
Java:
Раздел 7. Алгоритмы
Глава 3: Алгоритмы сортировки и подготовка данных
Назначение и стоимость сортировки: стратегическая инвестиция в данные
Квадратичные сортировки: фундамент понимания
Эффективные сортировки O(n log n): парадигмы и компромиссы
Практика : Реализовать сортировку выбором и сортировку слиянием для книг по году издания.
Глава 4: Эффективный поиск. Бинарный и не только
Бинарный поиск — принцип и реализация
Полезные статьи и видео:
ПОДКЛЮЧЕНИЕ GPT GO на ГОД!
Observability-as-Code в Spring Boot: Контракты и тесты для метрик, логов и трейсов
Как и всегда, задачи можно найти под тегом - #Tasks, вопросы с собеседований - #собеседование
Предыдущий пост(с 10.01 по 16.01)
Воскресный мотивационный пост:
не было мотивации
Запись встреч/видео:
Миграции с Flyway и LiquiBase. Основы управления БД
Обучающие статьи:
Java:
Раздел 7. Алгоритмы
Глава 3: Алгоритмы сортировки и подготовка данных
Назначение и стоимость сортировки: стратегическая инвестиция в данные
Квадратичные сортировки: фундамент понимания
Эффективные сортировки O(n log n): парадигмы и компромиссы
Практика : Реализовать сортировку выбором и сортировку слиянием для книг по году издания.
Глава 4: Эффективный поиск. Бинарный и не только
Бинарный поиск — принцип и реализация
Полезные статьи и видео:
ПОДКЛЮЧЕНИЕ GPT GO на ГОД!
Observability-as-Code в Spring Boot: Контракты и тесты для метрик, логов и трейсов
Как и всегда, задачи можно найти под тегом - #Tasks, вопросы с собеседований - #собеседование
Вот ранее большинство проголосовало за использование бусти. Допустим я в конец обнаглел и сделал это. Готовы ли вы платить за доступ?
Anonymous Poll
16%
Да, почему нет!
24%
Да ты охуел?
44%
Нет конечно же!
16%
Что я тут делаю?
История IT-технологий сегодня — 25 января
ℹ️ Кто родился в этот день
Семён Никола́евич Корса́ков (14 (25) января 1787 — 1 (13) декабря 1853) — русский дворянин, изобретатель механических устройств, «интеллектуальных машин» для информационного поиска и классификации, пионер применения перфорированных карт в информатике. Известен также своими работами по гомеопатии.
🌐 Знаковые события
1701 — в Москве основана школа математических и навигацких наук.
1955 — учёные Колумбийского университета создали атомные часы, показывающие время с погрешностью 1 секунда в 300 лет.
1979 — первый задокументированный случай гибели человека от промышленного робота: робот на автомобильном заводе в США убивает рабочего; инцидент стал поворотным пунктом для обсуждения безопасности робототехники, стандартов, контроля и взаимодействия человека с автоматизированными системами.
2004 — на поверхность Марса совершил посадку второй американский марсоход «Оппортьюнити».
#Biography #Birth_Date #Events #25Января
Семён Никола́евич Корса́ков (14 (25) января 1787 — 1 (13) декабря 1853) — русский дворянин, изобретатель механических устройств, «интеллектуальных машин» для информационного поиска и классификации, пионер применения перфорированных карт в информатике. Известен также своими работами по гомеопатии.
1701 — в Москве основана школа математических и навигацких наук.
1955 — учёные Колумбийского университета создали атомные часы, показывающие время с погрешностью 1 секунда в 300 лет.
1979 — первый задокументированный случай гибели человека от промышленного робота: робот на автомобильном заводе в США убивает рабочего; инцидент стал поворотным пунктом для обсуждения безопасности робототехники, стандартов, контроля и взаимодействия человека с автоматизированными системами.
2004 — на поверхность Марса совершил посадку второй американский марсоход «Оппортьюнити».
#Biography #Birth_Date #Events #25Января
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Потом не существует. Есть только сегодня и никогда
Очень знакомая и всеми любимая история:
“с понедельника не ем сладкое”,
“с нового года завязываю с курением”.
Это не лень.
Это практичный обман твоего мозга: пообещать, а после того как ты успокоился — ничего не менять.
Но что если ты решил: Со следующей недели, бросаю CS и сажусь за Java?
Жаль тебя обламывать, но с началом недели ты снова запустишь привычную игру.
Потом пообещаешь начать со следующей.
Потом ещё раз.
И ещё.
Почему потом — это когнитивный баг, а не лень
Когда ты говоришь себе с понедельника начну, мозг получает мгновенную награду. Дофаминовый выброс происходит не от действия, а от планирования действия. Ты уже почувствовал себя организованным, дисциплинированным — и мозг доволен. Зачем реально менять что-то, если удовольствие уже получено?
Это называется intention-action gap (разрыв намерения и действия). Намерения объясняют только 30-40% вариативности поведения, а в экспериментальных условиях влияние намерения на реальное действие падает до 15% (Rhodes & Dickau, 2012; McEachan et al., 2011).
Цифры, которые показывают масштаб иллюзии:
- 43% бросают новогодние обещания к концу января, 81% — не доживают до марта. «Quitter's Day» (день массового бросания) приходится на вторую пятницу января — обычно это 19-20 число (Drive Research, 2024; Strava Data, 2019)
- 88% людей признаются, что регулярно откладывают важные долгосрочные задачи, предпочитая сиюминутные удовольствия (исследование Journal of Consumer Research).
- По данным опроса Stack Overflow, ~40% программистов регулярно откладывают изучение нового языка или технологии, несмотря на понимание их необходимости для карьеры.
- Формирование устойчивой новой привычки, по данным Европейского журнала социальной психологии требует в среднем за 59–66 дней (не 21, как миф), а у некоторых — до 254 дней (Lally et al., European Journal of Social Psychology, 2010; Scientific American, 2024). При этом только 23% людей достигают автоматичности поведения.
Вот мои советы:
1. Старт должен быть легчайшим
Не планируй с понедельника учить Java по 3 часа. Планируй: Сегодня открою IntelliJ и напишу public static void main, который выводит 'Hello World'. Всё.
Если после этого захочешь закрыть — закрывай. Но 90% вероятности, что продолжишь.
Почему это сработает: Когда действие занимает <2 минут, сопротивление минимально ("2-minute rule", Clear, 2018).
2. Окружение важнее мотивации
Убери иконку CS с рабочего стола. Прямо сейчас. Перемести в папку, до которой нужно лезть.
Установи Java JDK и IDE заранее, пока есть желание. Если завтра придётся тратить 20 минут на установку — 80% вероятности, что не начнёшь.
Заблокируй себе возможность залезть в соцсети и ютуб (за исключением этого канала конечно же)
Почему это сработает: структурированный подход повышает успешность формирования привычки на 64%.
3. Поставь что-то на кон
Делаем бездействие болезненным.
(На свой страх и риск)
Денежные ставки: Обещай другу, что если не сядешь за Java в течение 24 часов после объявления, переводишь ему 1000 рублей. Работает лучше мотивации.
Социальный контракт: Публично заяви в соцсетях, чате единомышленников или коллег: «Я приступаю к изучению Java с сегодняшнего дня. Каждую пятницу буду публиковать отчет о прогрессе и выложу первый проект ровно через месяц». Страх опозориться — мощнейший двигатель. Публичное обязательство повышает выполнение на 37%.
Implementation intention: Не "буду учить Java", а "завтра в 9:00, сразу после кофе, открою IntelliJ и создам новый класс". Конкретика времени и места снижает вероятность отказа на 50%.
Почему это сработает: Мозг боится потерь сильнее, чем любит приобретения (loss aversion (Kahneman & Tversky, 1979; Ruggeri et al., Nature Human Behaviour, 2020)). Используй это.
ПОЙМИ:
"Потом" — заканчивается ровно в тот момент, когда ты, прочитав это, не закрываешь статью, а открываешь новую вкладку и гуглишь "Java Hello World пример".
Не со следующей недели. Не завтра.
А прямо сейчас.
😎
#motivation
Очень знакомая и всеми любимая история:
“с понедельника не ем сладкое”,
“с нового года завязываю с курением”.
Это не лень.
Это практичный обман твоего мозга: пообещать, а после того как ты успокоился — ничего не менять.
Но что если ты решил: Со следующей недели, бросаю CS и сажусь за Java?
Жаль тебя обламывать, но с началом недели ты снова запустишь привычную игру.
Потом пообещаешь начать со следующей.
Потом ещё раз.
И ещё.
Почему потом — это когнитивный баг, а не лень
Когда ты говоришь себе с понедельника начну, мозг получает мгновенную награду. Дофаминовый выброс происходит не от действия, а от планирования действия. Ты уже почувствовал себя организованным, дисциплинированным — и мозг доволен. Зачем реально менять что-то, если удовольствие уже получено?
Это называется intention-action gap (разрыв намерения и действия). Намерения объясняют только 30-40% вариативности поведения, а в экспериментальных условиях влияние намерения на реальное действие падает до 15% (Rhodes & Dickau, 2012; McEachan et al., 2011).
Цифры, которые показывают масштаб иллюзии:
- 43% бросают новогодние обещания к концу января, 81% — не доживают до марта. «Quitter's Day» (день массового бросания) приходится на вторую пятницу января — обычно это 19-20 число (Drive Research, 2024; Strava Data, 2019)
- 88% людей признаются, что регулярно откладывают важные долгосрочные задачи, предпочитая сиюминутные удовольствия (исследование Journal of Consumer Research).
- По данным опроса Stack Overflow, ~40% программистов регулярно откладывают изучение нового языка или технологии, несмотря на понимание их необходимости для карьеры.
- Формирование устойчивой новой привычки, по данным Европейского журнала социальной психологии требует в среднем за 59–66 дней (не 21, как миф), а у некоторых — до 254 дней (Lally et al., European Journal of Social Psychology, 2010; Scientific American, 2024). При этом только 23% людей достигают автоматичности поведения.
Вот мои советы:
1. Старт должен быть легчайшим
Не планируй с понедельника учить Java по 3 часа. Планируй: Сегодня открою IntelliJ и напишу public static void main, который выводит 'Hello World'. Всё.
Если после этого захочешь закрыть — закрывай. Но 90% вероятности, что продолжишь.
Почему это сработает: Когда действие занимает <2 минут, сопротивление минимально ("2-minute rule", Clear, 2018).
2. Окружение важнее мотивации
Убери иконку CS с рабочего стола. Прямо сейчас. Перемести в папку, до которой нужно лезть.
Установи Java JDK и IDE заранее, пока есть желание. Если завтра придётся тратить 20 минут на установку — 80% вероятности, что не начнёшь.
Заблокируй себе возможность залезть в соцсети и ютуб (за исключением этого канала конечно же)
Почему это сработает: структурированный подход повышает успешность формирования привычки на 64%.
3. Поставь что-то на кон
Делаем бездействие болезненным.
(На свой страх и риск)
Денежные ставки: Обещай другу, что если не сядешь за Java в течение 24 часов после объявления, переводишь ему 1000 рублей. Работает лучше мотивации.
Социальный контракт: Публично заяви в соцсетях, чате единомышленников или коллег: «Я приступаю к изучению Java с сегодняшнего дня. Каждую пятницу буду публиковать отчет о прогрессе и выложу первый проект ровно через месяц». Страх опозориться — мощнейший двигатель. Публичное обязательство повышает выполнение на 37%.
Implementation intention: Не "буду учить Java", а "завтра в 9:00, сразу после кофе, открою IntelliJ и создам новый класс". Конкретика времени и места снижает вероятность отказа на 50%.
Почему это сработает: Мозг боится потерь сильнее, чем любит приобретения (loss aversion (Kahneman & Tversky, 1979; Ruggeri et al., Nature Human Behaviour, 2020)). Используй это.
ПОЙМИ:
"Потом" — заканчивается ровно в тот момент, когда ты, прочитав это, не закрываешь статью, а открываешь новую вкладку и гуглишь "Java Hello World пример".
Не со следующей недели. Не завтра.
А прямо сейчас.
#motivation
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9👍3🤯1🆒1
История IT-технологий сегодня — 26 января
ℹ️ Кто родился в этот день
Фе́ликс Хаусдо́рф (нем. Felix Hausdorff, 8 ноября 1868, Бреслау — 26 января 1942, Бонн) — математик, создавший современную теорию топологических и метрических пространств; топология и меры Хаусдорфа важны для анализа данных, фракталов и многих теоретических разделов информатики.
🌐 Знаковые события
1983 — выход Lotus 1‑2‑3 (по хронологии — 26 января): один из первых «киллер‑приложений» для IBM PC; электронная таблица, объединившая расчёты, графику и базу данных, сделала PC стандартом для бизнеса и показала, как одно приложение может радикально ускорить цифровизацию предприятий.
#Biography #Birth_Date #Events #26Января
Фе́ликс Хаусдо́рф (нем. Felix Hausdorff, 8 ноября 1868, Бреслау — 26 января 1942, Бонн) — математик, создавший современную теорию топологических и метрических пространств; топология и меры Хаусдорфа важны для анализа данных, фракталов и многих теоретических разделов информатики.
1983 — выход Lotus 1‑2‑3 (по хронологии — 26 января): один из первых «киллер‑приложений» для IBM PC; электронная таблица, объединившая расчёты, графику и базу данных, сделала PC стандартом для бизнеса и показала, как одно приложение может радикально ускорить цифровизацию предприятий.
#Biography #Birth_Date #Events #26Января
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
3. Data Access Layer: осознанный выбор между JPA, JDBC и jOOQ
В этом видео мы разбираем один из самых фундаментальных архитектурных выборов в backend-разработке — подход к работе с базой данных.
Речь пойдёт не о синтаксисе и не о "как написать код", а о trade-offs, ответственности и стоимости владения каждого подхода.
Мы реализуем один и тот же use-case сохранения заказа тремя способами:
🔹 JDBC — полный контроль и максимальная ответственность
🔹 jOOQ — типобезопасный SQL и compile-time гарантии
🔹 JPA (Hibernate) — абстракция, скорость разработки и экосистема Spring
🔹 И немного дебага как всегда)
А затем осознанно выбираем JPA как production-стратегию для проекта OrderHub.
Исходный код проекта на GitHub очень ждет Ваших звезд.
Ссылка на Youtube
Ссылка на Рутьюб
Смотрите, ставьте лайки, подписывайтесь на каналы!✌️
В этом видео мы разбираем один из самых фундаментальных архитектурных выборов в backend-разработке — подход к работе с базой данных.
Речь пойдёт не о синтаксисе и не о "как написать код", а о trade-offs, ответственности и стоимости владения каждого подхода.
Мы реализуем один и тот же use-case сохранения заказа тремя способами:
🔹 JDBC — полный контроль и максимальная ответственность
🔹 jOOQ — типобезопасный SQL и compile-time гарантии
🔹 JPA (Hibernate) — абстракция, скорость разработки и экосистема Spring
🔹 И немного дебага как всегда)
А затем осознанно выбираем JPA как production-стратегию для проекта OrderHub.
Исходный код проекта на GitHub очень ждет Ваших звезд.
Ссылка на Youtube
Ссылка на Рутьюб
Смотрите, ставьте лайки, подписывайтесь на каналы!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍1
Глава 4: Эффективный поиск. Бинарный и не только
Модификации и родственные методы
Ранее мы рассмотрели классический бинарный поиск, который эффективно отвечает на вопрос «есть ли элемент X в массиве?» и возвращает случайную позицию в случае дубликатов. Но реальные задачи редко сводятся к такому простому запросу. Что делать, если вам нужно найти все книги с заданным названием в каталоге, где дубликаты вполне закономерны (например, несколько экземпляров одного издания)? А как насчет поиска диапазона — всех книг, выпущенных между 2020 и 2023 годом включительно? Или если вы знаете, что данные распределены не просто монотонно, а равномерно, и хотите использовать это свойство для ускорения?
Эти вопросы приводят нас к модификациям классического алгоритма и его альтернативам. Каждая модификация сохраняет логарифмическую природу, но меняет инварианты и условия завершения.
Поиск границ в массиве с дубликатами
Проблема неоднозначности стандартной реализации
Представьте массив книг, отсортированных по названию:
Вызываем Arrays.binarySearch(titles, "Война и мир"). Результат? Не определен. Javadoc гарантирует лишь, что будет возвращен какой-то индекс из диапазона дубликатов, если они существуют. Это недопустимо, когда вам нужен первый экземпляр для отображения в UI или последний для расчета границ диапазона.
Инвариант для поиска первого вхождения
Чтобы гарантированно найти самую левую позицию элемента, мы меняем логику завершения. Классический поиск завершается при точном совпадении array[mid] == target. Мы же должны продолжать поиск в левой половине даже после нахождения совпадения, потому что там может скрываться еще более ранний дубликат.
Новый инвариант: алгоритм поддерживает две зоны — переднюю, где все элементы строго меньше target, и заднюю, где элементы больше или равны target. Когда цикл завершается, указатель left будет указывать на первый элемент, равный target, или на позицию вставки, если target отсутствует.
#Java #для_новичков #beginner #algorithm #sorted #binary
Модификации и родственные методы
Ранее мы рассмотрели классический бинарный поиск, который эффективно отвечает на вопрос «есть ли элемент X в массиве?» и возвращает случайную позицию в случае дубликатов. Но реальные задачи редко сводятся к такому простому запросу. Что делать, если вам нужно найти все книги с заданным названием в каталоге, где дубликаты вполне закономерны (например, несколько экземпляров одного издания)? А как насчет поиска диапазона — всех книг, выпущенных между 2020 и 2023 годом включительно? Или если вы знаете, что данные распределены не просто монотонно, а равномерно, и хотите использовать это свойство для ускорения?
Эти вопросы приводят нас к модификациям классического алгоритма и его альтернативам. Каждая модификация сохраняет логарифмическую природу, но меняет инварианты и условия завершения.
Поиск границ в массиве с дубликатами
Проблема неоднозначности стандартной реализации
Представьте массив книг, отсортированных по названию:
String[] titles = {
"Война и мир", "Война и мир", "Война и мир",
"Гарри Поттер", "Гарри Поттер",
"Мастер и Маргарита"
};Вызываем Arrays.binarySearch(titles, "Война и мир"). Результат? Не определен. Javadoc гарантирует лишь, что будет возвращен какой-то индекс из диапазона дубликатов, если они существуют. Это недопустимо, когда вам нужен первый экземпляр для отображения в UI или последний для расчета границ диапазона.
Инвариант для поиска первого вхождения
Чтобы гарантированно найти самую левую позицию элемента, мы меняем логику завершения. Классический поиск завершается при точном совпадении array[mid] == target. Мы же должны продолжать поиск в левой половине даже после нахождения совпадения, потому что там может скрываться еще более ранний дубликат.
Новый инвариант: алгоритм поддерживает две зоны — переднюю, где все элементы строго меньше target, и заднюю, где элементы больше или равны target. Когда цикл завершается, указатель left будет указывать на первый элемент, равный target, или на позицию вставки, если target отсутствует.
#Java #для_новичков #beginner #algorithm #sorted #binary
👍3