Что выведет код?
#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
public class BinarySearchBounds {
/**
* Поиск первого вхождения целевого значения.
* Если элемент отсутствует, возвращает индекс, где он должен быть вставлен.
*
* @return индекс первого вхождения target или potential insertion point
*/
public static int findFirst(int[] array, int target) {
if (array == null) throw new IllegalArgumentException("Array cannot be null");
int left = 0;
int right = array.length - 1;
int result = -1; // Потенциальная позиция вставки
while (left <= right) {
int mid = left + (right - left) / 2;
int midValue = array[mid];
if (midValue < target) {
// Цель строго правее
left = mid + 1;
} else if (midValue > target) {
// Цель строго левее
right = mid - 1;
} else {
// Нашли совпадение, но продолжаем искать в левой половине
result = mid; // Запомнили потенциальный ответ
right = mid - 1; // Сдвинули границу влево
}
}
// Если result остался -1, target не найден. Можно вернуть left как точку вставки.
return result != -1 ? result : left;
}
/**
* Поиск последнего вхождения целевого значения.
*/
public static int findLast(int[] array, int target) {
if (array == null) throw new IllegalArgumentException("Array cannot be null");
int left = 0;
int right = array.length - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
int midValue = array[mid];
if (midValue < target) {
left = mid + 1;
} else if (midValue > target) {
right = mid - 1;
} else {
result = mid; // Запомнили потенциальный ответ
left = mid + 1; // Ключевое отличие: сдвигаемся вправо
}
}
return result != -1 ? result : left - 1; // left указывает после последнего <= target
}
}Ключевые отличия от классического поиска:
Сохранение состояния: Переменная result запоминает последнюю удачную позицию. Это нарушает чистоту инварианта, но необходимо для корректности.
Асимметричные действия: При нахождении target мы не выходим, а сдвигаем границу в сторону, противоположную от искомой границы. Для findFirst — влево, для findLast — вправо.
Выходное значение: В отсутствие элемента метод возвращает не -1, а точку вставки. Это делает API более универсальным для построения диапазонных запросов.
Поиск последнего вхождения: симметричная логика
Метод findLast зеркально отражает findFirst. Когда находится совпадение, мы продолжаем поиск в правой половине, потому что там может быть еще один дубликат. После завершения цикла left указывает на первый элемент строго больше target, поэтому left - 1 — это последний элемент, меньший или равный target.
#Java #для_новичков #beginner #algorithm #sorted #binary
👍3
Поиск диапазона
От границ к диапазону: композиция операций
Теперь, когда у нас есть инструменты для поиска границ, задача «найти все книги, изданные в 2020-2023 годах» решается композицией:
Анализ производительности: Поиск диапазона требует двух бинарных поисков — O(log n) + O(log n) = O(log n). После этого мы получаем непосредственный доступ к результату за O(1). Если результатов много (k элементов), итоговая сложность O(log n + k). Это намного эффективнее линейного сканирования всей библиотеки O(n).
Важное замечание: Метод возвращает subList, который является view исходного массива. Изменения в исходном массиве отразятся в результате. Для защиты нужно скопировать: new ArrayList<>(Arrays.asList(...).subList(...)).
#Java #для_новичков #beginner #algorithm #sorted #binary
От границ к диапазону: композиция операций
Теперь, когда у нас есть инструменты для поиска границ, задача «найти все книги, изданные в 2020-2023 годах» решается композицией:
public class LibraryRangeSearch {
static class Book implements Comparable<Book> {
String title;
int year;
// Конструктор, геттеры...
@Override
public int compareTo(Book other) {
return Integer.compare(this.year, other.year);
}
}
/**
* Поиск всех книг в заданном диапазоне лет [startYear, endYear].
* Возвращает подмассив (views) для экономии памяти.
*/
public static List<Book> findBooksByYearRange(Book[] library, int startYear, int endYear) {
if (library == null || startYear > endYear) {
return Collections.emptyList();
}
// Создаем фиктивные книги-границы для поиска
Book startDummy = new Book("", startYear);
Book endDummy = new Book("", endYear);
// Находим первую книгу с year >= startYear
int leftIndex = findFirstIndex(library, startDummy);
// Находим последнюю книгу с year <= endYear
int rightIndex = findLastIndex(library, endDummy);
if (leftIndex == -1 || rightIndex == -1 || leftIndex > rightIndex) {
return Collections.emptyList();
}
// Возвращаем view для избежания копирования
return Arrays.asList(library).subList(leftIndex, rightIndex + 1);
}
// Адаптер для работы с Comparable объектами
private static int findFirstIndex(Book[] array, Book target) {
int left = 0, right = array.length - 1, result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
int cmp = array[mid].compareTo(target);
if (cmp < 0) left = mid + 1;
else if (cmp > 0) right = mid - 1;
else { result = mid; right = mid - 1; }
}
return result != -1 ? result : left;
}
private static int findLastIndex(Book[] array, Book target) {
int left = 0, right = array.length - 1, result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
int cmp = array[mid].compareTo(target);
if (cmp < 0) left = mid + 1;
else if (cmp > 0) right = mid - 1;
else { result = mid; left = mid + 1; }
}
return result != -1 ? result : left - 1;
}
}Анализ производительности: Поиск диапазона требует двух бинарных поисков — O(log n) + O(log n) = O(log n). После этого мы получаем непосредственный доступ к результату за O(1). Если результатов много (k элементов), итоговая сложность O(log n + k). Это намного эффективнее линейного сканирования всей библиотеки O(n).
Важное замечание: Метод возвращает subList, который является view исходного массива. Изменения в исходном массиве отразятся в результате. Для защиты нужно скопировать: new ArrayList<>(Arrays.asList(...).subList(...)).
#Java #для_новичков #beginner #algorithm #sorted #binary
👍3
Альтернативный подход: поиск нижней границы + линейное сбирание
Если вы знаете, что диапазон редко содержит много элементов (например, книги за конкретный год в библиотеке с редкими изданиями), можно оптимизировать:
Этот подход имеет сложность O(log n + k) в худшем случае, но с меньшими константами, потому что второй бинарный поиск заменен на последовательное чтение (кэш-приятно).
Интерполяционный поиск — когда данные говорят сами за себя
Интуиция: зачем всегда делить пополам?
Представьте телефонный справочник, где фамилии распределены равномерно по алфавиту. Ищете «Иванов». Классический бинарный поиск откроет справочник ровно посередине — на букве «М», затем на «Г», затем на «Д»... Вместо этого можно интерполировать: поскольку «И» находится примерно на 10% алфавита, сразу открыть страницу на 10% от общего объема.
Интерполяционный поиск заменяет слепое деление пополам на адресный расчет вероятной позиции на основе значений границ.
Математическая формула интерполяции
Если array[left] и array[right] известны, и target лежит между ними, то при равномерном распределении его позиция должна быть пропорциональна:
Это формула линейной интерполяции. Она вычисляет, насколько далеко target находится от левой границы в долях от общего диапазона значений, и применяет этот же коэффициент к индексам.
#Java #для_новичков #beginner #algorithm #sorted #binary
Если вы знаете, что диапазон редко содержит много элементов (например, книги за конкретный год в библиотеке с редкими изданиями), можно оптимизировать:
public static List<Book> findBooksByYearRangeOptimized(Book[] library, int startYear, int endYear) {
int startIdx = findFirstIndex(library, new Book("", startYear));
if (startIdx == -1 || startIdx >= library.length) return Collections.emptyList();
List<Book> result = new ArrayList<>();
for (int i = startIdx; i < library.length && library[i].year <= endYear; i++) {
result.add(library[i]);
}
return result;
}Этот подход имеет сложность O(log n + k) в худшем случае, но с меньшими константами, потому что второй бинарный поиск заменен на последовательное чтение (кэш-приятно).
Интерполяционный поиск — когда данные говорят сами за себя
Интуиция: зачем всегда делить пополам?
Представьте телефонный справочник, где фамилии распределены равномерно по алфавиту. Ищете «Иванов». Классический бинарный поиск откроет справочник ровно посередине — на букве «М», затем на «Г», затем на «Д»... Вместо этого можно интерполировать: поскольку «И» находится примерно на 10% алфавита, сразу открыть страницу на 10% от общего объема.
Интерполяционный поиск заменяет слепое деление пополам на адресный расчет вероятной позиции на основе значений границ.
Математическая формула интерполяции
Если array[left] и array[right] известны, и target лежит между ними, то при равномерном распределении его позиция должна быть пропорциональна:
mid = left + (target - array[left]) * (right - left) / (array[right] - array[left])
Это формула линейной интерполяции. Она вычисляет, насколько далеко target находится от левой границы в долях от общего диапазона значений, и применяет этот же коэффициент к индексам.
#Java #для_новичков #beginner #algorithm #sorted #binary
👍2
Реализация и инварианты
Критические условия применимости:
Равномерное распределение: Формула работает, если разность между соседними элементами примерно постоянна. Если данные сгущаются в некоторых зонах (например, 90% книг изданы в 2020-х, а остальные — растянуты на 50 лет), интерполяция будет постоянно ошибаться, откатываясь к бинарному поведению или хуже.
Отсутствие повторений на границах: Если array[left] == array[right], формула приводит к делению на ноль. В этом случае алгоритм должен деградировать к линейному поиску в этом поддиапазоне.
Целочисленное переполнение: Выражение (target - array[left]) * (right - left) может переполнить int при больших значениях. Для production-кода рекомендуется использовать long для промежуточных расчетов.
Анализ сложности и парадоксы
Средний случай: При равномерном распределении интерполяционный поиск достигает O(log log n). Это практически константа: для массива из 1 миллиарда элементов потребуется ~5 итераций. Это достигается за счет того, что каждый шаг не просто делит диапазон пополам, а приближается к цели экспоненциально быстро.
Худший случай: Если данные неравномерны (например, [1, 2, 3, 4, 5, 1000, 1001, 1002, 1003]), интерполяция может снова и снова попадать в «пустые» зоны, требуя O(n) сравнений. Это хуже бинарного поиска.
Практический вывод: Интерполяционный поиск имеет смысл применять только тогда, когда вы точно знаете характер распределения данных и уверены в его равномерности. В остальных случаях бинарный поиск более надежен. В Java стандартная библиотека не содержит интерполяционного поиска из-за его узкой применимости и риска деградации.
#Java #для_новичков #beginner #algorithm #sorted #binary
public class InterpolationSearch {
/**
* Интерполяционный поиск для равномерно распределенных целочисленных данных.
* ВНИМАНИЕ: требует, чтобы array[left] < array[right] и данные были равномерными!
*/
public static int search(int[] array, int target) {
if (array == null) throw new IllegalArgumentException("Array cannot be null");
int left = 0;
int right = array.length - 1;
// Условие array[left] <= target <= array[right] критично для формулы
while (left <= right && target >= array[left] && target <= array[right]) {
// Если диапазон схлопнулся, переходим к линейному поиску
if (array[left] == array[right]) {
if (array[left] == target) return left;
break; // Не найден
}
// Интерполяция позиции
// Предотвращаем деление на ноль проверкой выше
int mid = left + (target - array[left]) * (right - left) / (array[right] - array[left]);
// Защита от выхода за границы (возможна при неравномерных данных)
mid = Math.max(left, Math.min(mid, right));
int midValue = array[mid];
if (midValue < target) {
left = mid + 1;
} else if (midValue > target) {
right = mid - 1;
} else {
return mid;
}
}
// Пост-проверка границ
if (left <= right && array[left] == target) return left;
return -1;
}
}Критические условия применимости:
Равномерное распределение: Формула работает, если разность между соседними элементами примерно постоянна. Если данные сгущаются в некоторых зонах (например, 90% книг изданы в 2020-х, а остальные — растянуты на 50 лет), интерполяция будет постоянно ошибаться, откатываясь к бинарному поведению или хуже.
Отсутствие повторений на границах: Если array[left] == array[right], формула приводит к делению на ноль. В этом случае алгоритм должен деградировать к линейному поиску в этом поддиапазоне.
Целочисленное переполнение: Выражение (target - array[left]) * (right - left) может переполнить int при больших значениях. Для production-кода рекомендуется использовать long для промежуточных расчетов.
Анализ сложности и парадоксы
Средний случай: При равномерном распределении интерполяционный поиск достигает O(log log n). Это практически константа: для массива из 1 миллиарда элементов потребуется ~5 итераций. Это достигается за счет того, что каждый шаг не просто делит диапазон пополам, а приближается к цели экспоненциально быстро.
Худший случай: Если данные неравномерны (например, [1, 2, 3, 4, 5, 1000, 1001, 1002, 1003]), интерполяция может снова и снова попадать в «пустые» зоны, требуя O(n) сравнений. Это хуже бинарного поиска.
Практический вывод: Интерполяционный поиск имеет смысл применять только тогда, когда вы точно знаете характер распределения данных и уверены в его равномерности. В остальных случаях бинарный поиск более надежен. В Java стандартная библиотека не содержит интерполяционного поиска из-за его узкой применимости и риска деградации.
#Java #для_новичков #beginner #algorithm #sorted #binary
👍3
Что выведет код?
#Tasks
public class Task260126 {
public static void main(String[] args) {
int[] arr = {2, 4, 6, 8, 10, 12, 14, 16, 18, 1000};
int index = interpolationSearch260126(arr, 2);
System.out.println(index);
}
static int interpolationSearch260126(int[] arr, int target) {
int low = 0;
int high = arr.length - 1;
while (low <= high && target >= arr[low] && target <= arr[high]) {
int pos = low + ((target - arr[low]) * (high - low)) / (arr[high] - arr[low]);
if (arr[pos] == target) return pos;
if (arr[pos] < target) low = pos + 1;
else high = pos - 1;
}
return -1;
}
}#Tasks
👍2
👍3
Java for Beginner
3. Data Access Layer: осознанный выбор между JPA, JDBC и jOOQ В этом видео мы разбираем один из самых фундаментальных архитектурных выборов в backend-разработке — подход к работе с базой данных. Речь пойдёт не о синтаксисе и не о "как написать код", а о…
Ну хоть поделитесь, как вам видео? Полезно аль не очень?☺️
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Почему finalize() опасен даже если он “работает”? 🤓
Ответ:
finalize вызывается не сразу и не гарантирован вообще.
Объект может долго оставаться в памяти, даже если он логически мёртв. Ошибка в finalize может полностью блокировать сборку мусора для объекта. Кроме того, finalize выполняется в отдельном потоке GC, что усложняет отладку.
Именно поэтому finalize признан deprecated, а управление ресурсами должно выполняться явно через try-with-resources.
#собеседование
Ответ:
Объект может долго оставаться в памяти, даже если он логически мёртв. Ошибка в finalize может полностью блокировать сборку мусора для объекта. Кроме того, finalize выполняется в отдельном потоке GC, что усложняет отладку.
Именно поэтому finalize признан deprecated, а управление ресурсами должно выполняться явно через try-with-resources.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
История IT-технологий сегодня — 27 января
ℹ️ Кто родился в этот день
Лью́ис Кэ́рролл (англ. Lewis Carroll, настоящее имя Чарльз Лютвидж До́джсон, или Чарльз Латуидж До́джсон (традиционная русская передача; сам Кэрролл произносил свою фамилию Додсон, ˈdɒdsən[11], ряд современных словарей даёт произношение ˈdɒdʒsən[12][13]), Charles Lutwidge Dodgson; 27 января 1832 — 14 января 1898) — математик и логик, работал над логическими диограммами, алгеброй логики и теорией вероятностей; его логические идеи и подход к формализации рассуждений — часть исторического фундамента логики, на которой стоит теоретическая информатика. Наиболее известные произведения — «Приключения Алисы в Стране чудес» и «Алиса в Зазеркалье», а также юмористическая поэма «Охота на Снарка».
🌐 Знаковые события
1994 — Джим Кларк уходит из Silicon Graphics и создаёт Mosaic Communications (позже Netscape): компания стала одной из первых, кто коммерциализировал веб‑браузер, что сильно ускорило развитие и распространение интернета и веб‑технологий. Доля пользователей Netscape достигла пика в середине 1990-х годов, но к концу 2006 года упала до менее чем одного процента.
#Biography #Birth_Date #Events #27Января
Лью́ис Кэ́рролл (англ. Lewis Carroll, настоящее имя Чарльз Лютвидж До́джсон, или Чарльз Латуидж До́джсон (традиционная русская передача; сам Кэрролл произносил свою фамилию Додсон, ˈdɒdsən[11], ряд современных словарей даёт произношение ˈdɒdʒsən[12][13]), Charles Lutwidge Dodgson; 27 января 1832 — 14 января 1898) — математик и логик, работал над логическими диограммами, алгеброй логики и теорией вероятностей; его логические идеи и подход к формализации рассуждений — часть исторического фундамента логики, на которой стоит теоретическая информатика. Наиболее известные произведения — «Приключения Алисы в Стране чудес» и «Алиса в Зазеркалье», а также юмористическая поэма «Охота на Снарка».
1994 — Джим Кларк уходит из Silicon Graphics и создаёт Mosaic Communications (позже Netscape): компания стала одной из первых, кто коммерциализировал веб‑браузер, что сильно ускорило развитие и распространение интернета и веб‑технологий. Доля пользователей Netscape достигла пика в середине 1990-х годов, но к концу 2006 года упала до менее чем одного процента.
#Biography #Birth_Date #Events #27Января
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Глава 4: Эффективный поиск. Бинарный и не только
Практика
Сегодня мы применим знания о эффективном поиске на проекте «Библиотека».
Мы отсортируем список книг по названию, реализуем бинарный поиск для нахождения первой книги заданного автора (в отсортированном списке), и проведём сравнение времени выполнения линейного и бинарного поиска на коллекциях разных размеров. Это поможет наглядно увидеть преимущества логарифмического поиска O(log n) над линейным O(n), понять предпосылки (сортировка данных) и границы применимости (когда сортировка окупается).
Подготовка к уроку
Перед началом убедитесь, что проект готов, и вспомните ключевые концепции:
Бинарный поиск работает только на отсортированных данных, делит интервал пополам.
Линейный поиск — перебор O(n), всегда работает.
Сортировка O(n log n) — предпосылка для бинарного.
Откройте проект «Библиотека»: Убедитесь, что List<Book> books содержит достаточно книг (добавьте метод для генерации тестовых данных).
Импортируйте пакеты: java.util.Arrays (для бинарного поиска), java.util.Random (для генерации данных).
Генерация больших данных: Создайте метод generateBooks(int size) для создания списков размером 100, 10 000, 1 000 000 (используйте Random для title/author/year).
Отсортировать книги по названию
Обновите Book для Comparable (если не сделано): Реализуйте compareTo по title (this.title.compareTo(other.title)).
Создайте метод sortByTitle(): Используйте Collections.sort(books) — сортирует по Comparable (названию).
Вывод: После сортировки вызовите printAllBooks() для проверки.
Реализовать бинарный поиск для нахождения первой книги заданного автора
Отсортируйте по автору: Создайте Comparator<Book> byAuthor = Comparator.comparing(Book::getAuthor);, затем books.sort(byAuthor).
Реализуйте метод findFirstBookByAuthor(String author):
Используйте Arrays.binarySearch, но поскольку books — List, преобразуйте в массив или реализуйте вручную.
Вручную:
Если не найден — return null.
Проверка: После сортировки вызовите метод, выведите найденную книгу.
Сравнить время линейного и бинарного поиска на разных размерах
Реализуйте линейный поиск: Метод linearSearchByAuthor(String author) — for-each, если совпадение — return book.
Измерение времени: Используйте System.nanoTime() before/after.
Тест на размерах:
Для 100: generateBooks(100), sortByAuthor, time linear vs binary (поиск рандомного автора).
Для 10 000 и 1 000 000: Аналогично, усредните по 100 запускам.
Выводите: "Для n=[size]: Линейный: [time ns], Бинарный: [time ns]".
Анализ: Точка окупаемости сортировки + бинарный поиск vs множественный линейный поиск
Точка окупаемости — момент, когда стоимость сортировки + m бинарных поисков становится меньше m линейных поисков.
Расчёт:
Линейный: O(n) per search → m * n
Бинарный: O(n log n) sort + m * log n
Окупаемость: n log n + m log n < m n → m > (n log n) / (n - log n) ≈ log n (для больших n)
Пример: n = 1000, log n ≈ 10 — окупаемость после ~10 поисков.
n = 1 млн, log n ≈ 20 — после ~20 поисков.
Граница: Для малого m или n — линейный дешевле (нет сортировки).
В библиотеке: Если поиски редки — линейный; если часты — sort + binary.
#Java #для_новичков #beginner #algorithm #sorted #binary #практика
Практика
Сегодня мы применим знания о эффективном поиске на проекте «Библиотека».
Мы отсортируем список книг по названию, реализуем бинарный поиск для нахождения первой книги заданного автора (в отсортированном списке), и проведём сравнение времени выполнения линейного и бинарного поиска на коллекциях разных размеров. Это поможет наглядно увидеть преимущества логарифмического поиска O(log n) над линейным O(n), понять предпосылки (сортировка данных) и границы применимости (когда сортировка окупается).
Подготовка к уроку
Перед началом убедитесь, что проект готов, и вспомните ключевые концепции:
Бинарный поиск работает только на отсортированных данных, делит интервал пополам.
Линейный поиск — перебор O(n), всегда работает.
Сортировка O(n log n) — предпосылка для бинарного.
Откройте проект «Библиотека»: Убедитесь, что List<Book> books содержит достаточно книг (добавьте метод для генерации тестовых данных).
Импортируйте пакеты: java.util.Arrays (для бинарного поиска), java.util.Random (для генерации данных).
Генерация больших данных: Создайте метод generateBooks(int size) для создания списков размером 100, 10 000, 1 000 000 (используйте Random для title/author/year).
Отсортировать книги по названию
Обновите Book для Comparable (если не сделано): Реализуйте compareTo по title (this.title.compareTo(other.title)).
Создайте метод sortByTitle(): Используйте Collections.sort(books) — сортирует по Comparable (названию).
Вывод: После сортировки вызовите printAllBooks() для проверки.
Реализовать бинарный поиск для нахождения первой книги заданного автора
Отсортируйте по автору: Создайте Comparator<Book> byAuthor = Comparator.comparing(Book::getAuthor);, затем books.sort(byAuthor).
Реализуйте метод findFirstBookByAuthor(String author):
Используйте Arrays.binarySearch, но поскольку books — List, преобразуйте в массив или реализуйте вручную.
Вручную:
int low = 0, high = books.size() - 1;
while (low <= high) {
mid = (low + high) / 2; cmp = books.get(mid).getAuthor().compareTo(author);
if (cmp < 0) low = mid + 1;
else if (cmp > 0) high = mid - 1;
else { // Найден, найти первый:
while (mid > 0 && books.get(mid-1).getAuthor().equals(author))
mid--;
return books.get(mid);
}
}
Если не найден — return null.
Проверка: После сортировки вызовите метод, выведите найденную книгу.
Сравнить время линейного и бинарного поиска на разных размерах
Реализуйте линейный поиск: Метод linearSearchByAuthor(String author) — for-each, если совпадение — return book.
Измерение времени: Используйте System.nanoTime() before/after.
Тест на размерах:
Для 100: generateBooks(100), sortByAuthor, time linear vs binary (поиск рандомного автора).
Для 10 000 и 1 000 000: Аналогично, усредните по 100 запускам.
Выводите: "Для n=[size]: Линейный: [time ns], Бинарный: [time ns]".
Анализ: Точка окупаемости сортировки + бинарный поиск vs множественный линейный поиск
Точка окупаемости — момент, когда стоимость сортировки + m бинарных поисков становится меньше m линейных поисков.
Расчёт:
Линейный: O(n) per search → m * n
Бинарный: O(n log n) sort + m * log n
Окупаемость: n log n + m log n < m n → m > (n log n) / (n - log n) ≈ log n (для больших n)
Пример: n = 1000, log n ≈ 10 — окупаемость после ~10 поисков.
n = 1 млн, log n ≈ 20 — после ~20 поисков.
Граница: Для малого m или n — линейный дешевле (нет сортировки).
В библиотеке: Если поиски редки — линейный; если часты — sort + binary.
#Java #для_новичков #beginner #algorithm #sorted #binary #практика
👍4