Oh1

Members
  • Публикаций

    33
  • Зарегистрирован

  • Посещение

  • Победитель дней

    1

Oh1 стал победителем дня 6 января

Oh1 имел наиболее популярный контент!

Репутация

1 Neutral

1 Подписчик

Информация о Oh1

  • Звание
    не местый

Посетители профиля

778 просмотров профиля
  1. Почему C# программисты скоро будут нарасхват C# программисты становятся более востребованными благодаря развивающейся экосистеме языка. На нём пишут бэкенд, приложения, игры. Разберёмся, в чём причина популярности языка. Знакомьтесь, C#! C# обладает выразительным синтаксисом, и его легко изучать. Фигурные скобки узнают все, кто работал в C, C++ или Java. Знакомы с одним из этих языков? Тогда вы быстро начнёте продуктивную работу в C#. Синтаксис языка устраняет сложности C++ и предоставляет такие мощные возможности, как обнуляемые значения типов, перечисления, делегаты, лямбда-выражения и прямой доступ к памяти. C# поддерживает универсальные методы и типы, которые повышают безопасность типов и производительность. Итераторы позволяют классам создателей коллекций определять кастомные поведения итераций, которые легко использовать в клиентском коде. Выражения интегрированного языка запросов (LINQ) делают строго типизированный запрос первоклассной конструкцией языка. C# – объектно-ориентированный язык, а значит, поддерживает инкапсуляцию, наследование и полиморфизм. Класс может наследоваться напрямую от одного родительского класса, но может реализовывать любое число интерфейсов. Методы, которые переопределяют виртуальные методы в родительском классе, требуют указания ключевого слова override для предотвращения случайного переопределения. В дополнение ко всем плюшкам ООП язык C# облегчает разработку программных компонентов посредством нескольких инновационных конструкций языка: Инкапсулированные сигнатуры методов, названные делегатами, включают оповещения о безопасности типов. Свойства служат акцессорами к переменным закрытых частей. Атрибуты предоставляют декларативные метаданные о типах во время исполнения. Строчные документационные комментарии XML. Интегрированный язык запросов (LINQ) предоставляет встроенные возможности запросов между различными источниками данных. Нужно взаимодействовать с другими программами Windows, например, с COM-объектами или со встроенными Win32 DLL? Сделайте это с помощью C# через процесс «Interop». Interop позволяет программам, написанным на C#, делать почти всё, что доступно программам, написанным на C++. C# поддерживает даже указатели и концепцию «небезопасного» кода для случаев, когда прямой доступ к памяти абсолютно критичен. Причём тут .NET? Программы C# выполняются поверх .NET Framework – встроенного компонента Windows, который включает в себя виртуальную среду выполнения, называемую общеязыковой исполняющей средой (CLR), и единый набор классовых библиотек. CLR – это коммерческая реализация общеязыковой инфраструктуры (CLI). Она служит основой для создания исполняемых файлов и окружений разработки, в которых языки и библиотеки работают совместно и незаметно. Исходный код, написанный на C#, компилируется в промежуточный язык (IL), который удовлетворяет спецификации CLI. Код IL и такие ресурсы, как растровые изображения и строки, хранятся на диске в виде исполняемого файла – сборки, обычно с расширениями .exe и .dll. Сборка содержит манифест, который предоставляет информацию о типах, версии, культуре и требованиях безопасности сборки. При запуске программы сборка C# загружается в CLR, которая совершает определённые действия, исходя из информации в манифесте. Если требования безопасности удовлетворяют CLR, она производит JIT-компиляцию, чтобы конвертировать код IL во встроенные машинные инструкции. CLR предоставляет и другие службы, связанные с автоматической сборкой мусора, обработкой исключений и управлением ресурсами. Код, выполняемый в CLR, иногда называют «управляемым кодом» для сравнения с «неуправляемым кодом», который компилируется во встроенный машинный язык, нацеленный на определённую систему. Вот отношение между временем компиляции и временем выполнения исходников C#, классовых библиотек .NET Framework, сборок и CLR: Языковая совместимость – ключевая особенность .NET Framework. Так как IL-код производится на C#, компилятор соответствует общей спецификации типов (CTS). IL код, сгенерированный из C#, может взаимодействовать с более чем двадцатью другими CTS-совместимыми языками. Одиночная сборка может содержать множество модулей, а типы могут ссылаться друг на друга так, будто они написаны на одном языке. .NET Core – кроссплатформенный .NET Версия Core – это модульная кроссплатформенная open source реализация стандарта .NET общего назначения. Она содержит множество API из .NET Framework и включает среду выполнения, фреймворк, а также инструменты, поддерживающие различные ОС и процессоры. Реализация находилась под управлением наработок из ASP.NET Core, но требовала более современного подхода. Она может использоваться для различных устройств, в облаке и для встроенных/IoT сценариев. Вот основные характеристики .NET Core: Кроссплатформенность: .NET Core предоставляет ключевую функциональность для реализации нужных особенностей вашего приложения, а также возможность повторного использования кода вне зависимости от вашей платформы. Она поддерживает Windows, Linux и macOS. Вы можете писать приложения и библиотеки, которые запускаются на всех поддерживаемых ОС без изменений. Open source: .NET Core – один из многих проектов, который находится под управлением .NET Foundation и доступен на GitHub. Использование .NET Core в качестве open source проекта способствует прозрачному процессу разработки и развитию активного, вовлечённого сообщества. Гибкий деплой: есть два главных способа деплоя вашего приложения: зависимый от фреймворка и автономный. В первом случае устанавливаются только ваше приложение и зависимости. Приложение зависит от глобальной версии .NET Core. Во втором случае версия .NET Core, которая использовалась для сборки вашего приложения, деплоится вместе с приложением и зависимостями, и может выполняться параллельно другим версиям. Модульность: .NET Core обладает модульностью потому, что выпускается через NuGet в меньших пакетах сборок. В отличие от одной большой сборки со всей встроенной функциональностью, .NET Core доступен в виде меньших функционально-ориентированных пакетов. Это приводит к более гибкой модели разработки, позволяет оптимизировать приложение, включая только необходимые пакеты NuGet. Преимущества меньшего поля покрытия приложения отражаются в повышенной безопасности, менее требовательном обслуживании, высокой производительности и в сниженных затратах по принципу «плати только за то, чем пользуешься». Благодаря .NET Core C# программисты могут разрабатывать не только на Windows. Xamarin: мобильная разработка на C#? Легко! Многие думают о разработке под Android и iOS в контексте таких встроенных языков, как Objective-C, Swift, Java и Kotlin. Xamarin позволяет разрабатывать на C#, используя библиотеки и рантайм, которые работают на iOS, Android и Windows. При этом компилируются встроенные (не интерпретированные) приложения с достаточной производительностью для игр. Xamarin совмещает все возможности встроенных платформ и добавляет несколько мощных особенностей: Полная привязка к базовым SDK: Xamarin содержит привязки практически ко всем базовым платформенным SDK от iOS и Android. Вдобавок эти привязки строго типизированы, что облегчает использование и навигацию, а также включает строгую проверку типов во время компиляции и разработки. Interop для Objective-C, Java, C и C++: Xamarin содержит средства для прямого вызова библиотек Objective-C, Java, C, and C++, а значит, вы свободны использовать сторонний код, не изобретая велосипед. Таким образом, вы воспользуетесь преимуществами существующих библиотек iOS и Android, написанных на Objective-C, Java или C/C++. Современные языковые конструкции: приложения Xamarin пишутся на C#, современном языке, который содержит существенные улучшения по сравнению с Objective-C и Java. Например, особенности динамического языка, лямбды, LINQ, функции параллельного программирования, дженерики и многое другое. Библиотека базовых классов (BCL): приложения Xamarin используют .NET BCL, большую коллекцию классов, которая имеет такие функции, как XML, базы данных, сериализацию, IO, строки и поддержку сети. Существующий код C# можно скомпилировать для дальнейшего использования в приложении, что открывает доступ к тысячам библиотек, которые могут делать то, что недоступно в BCL. Современная IDE: Xamarin использует Visual Studio на macOS и Windows. Обе версии обладают такими современными функциями, как автозавершение кода, системой управления проектом, библиотекой шаблонов, встроенным контролем версий. Поддержка мобильной кроссплатформенности: Xamarin поддерживает iOS, Android и Windows. Приложения могут иметь до 90% общего кода. Библиотека Xamarin.Mobile предлагает унифицированный API для доступа к ресурсам всех платформ. C# программисты могут разрабатывать кроссплатформенные мобильные приложения на Xamarin! C# используется в машинном обучении У вас уже есть опыт в .NET, и вы хотите освоить машинное обучение? ML.NET – это кроссплатформенный фреймворк машинного обучения с открытым исходным кодом. Он позволяет создавать кастомные ML-модели на C#, не покидая экосистему .NET. Вы можете использовать весь ваш опыт и багаж знаний, накопленный за время работы с .NET, чтобы интегрировать машинное обучение в существующие десктопные, игровые, мобильные или веб-приложения, и даже в IoT-устройства. Вот что можно сделать с ML.NET: Классификация/категоризация: разделяйте пользовательский фидбэк на позитивную и негативную категории автоматически. Регрессия/Прогнозирование: непрерывных значений: прогнозируйте цены на недвижимость исходя из размеров и местоположения. Обнаружения аномалий: обнаруживайте мошеннические банковские операции. Рекомендации: предлагайте продукты онлайн-покупателям исходя из их истории покупок. Отдельно про игры Unity – самый популярный игровой движок для разработки инди-игр, и он использует код на C#. Интересно, что сам движок написан на C++, поэтому он освобождает программиста от необходимости разбираться со скоростью исполнения, что идеально для новичков. Есть и другие движки с поддержкой C#: Monogame позволяет «написать игру один раз и играть везде»; WAVE – кроссплатформенный движок; Xenko – довольно мощный кроссплатформенный движок; Duality поддерживает только 2D; Xna – официальный движок от Microsoft; Flatredball – 2D-движок. Все перечисленные движки позволяют писать игровой код на C#, но Unity определённо занял особое место в коммерческой разработке. Достаточно вспомнить такие игры, как Rust и Forest – они написаны на Unity, а значит и на C#. Игра Rust написана на Unity C# программисты имеют большие возможности в геймдеве! Как всё это влияет на рынок труда? Возможно, C# программисты не ринулись строем писать мобильные или веб-приложения. Но посмотрите, какие возможности открываются для тех, кто уже знает C#. К тому же, Microsoft планируют к 2021 году объединить .NET Core и .NET Framework совместно с фишками вроде Windows Presentation Foundation (WPF). C# программисты больше не ограничены платформой Windows. Сегодня они могут смело пробовать себя в роли мобильных, игровых или веб-разработчиков. Выгода для компаний и стартапов ещё очевидней: можно использовать одну команду разработчиков для всех видов приложений. Улучшается поддержка кода, так как вся база написана на C#, включая серверный бэкенд. Отдельно стоит отметить машинное обучение – это огромные перспективы для C# разработчиков и для самого языка.
  2. Язык программирования Python 3 для начинающих и чайников Язык программирования Python 3 — это мощный инструмент для создания программ самого разнообразного назначения, доступный даже для новичков. С его помощью можно решать задачи различных типов. Этот сайт призван помочь начинающим и чайникам научиться программировать на python 3. Также здесь можно подробнее узнать об особенностях функционирования этого языка. Язык Python обладает некоторыми примечательными особенностями, которые обуславливают его широкое распространение. Поэтому прежде чем изучать python, следует рассказать о его достоинствах и недостатках. Python 3: преимущества и недостатки языка Python - интерпретируемый язык программирования. С одной стороны, это позволяет значительно упростить отладку программ, с другой - обуславливает сравнительно низкую скорость выполнения. Динамическая типизация. В python не надо заранее объявлять тип переменной, что очень удобно при разработке. Хорошая поддержка модульности. Вы можете легко написать свой модуль и использовать его в других программах. Встроенная поддержка Unicode в строках. В Python необязательно писать всё на английском языке, в программах вполне может использоваться ваш родной язык. Поддержка объектно-ориентированного программирования. При этом его реализация в python является одной из самых понятных. Автоматическая сборка мусора, отсутствие утечек памяти. Интеграция с C/C++, если возможностей python недостаточно. Понятный и лаконичный синтаксис, способствующий ясному отображению кода. Удобная система функций позволяет при грамотном подходе создавать код, в котором будет легко разобраться другому человеку в случае необходимости. Также вы сможете научиться читать программы и модули, написанные другими людьми. Огромное количество модулей, как входящих в стандартную поставку Python 3, так и сторонних. В некоторых случаях для написания программы достаточно лишь найти подходящие модули и правильно их скомбинировать. Таким образом, вы можете думать о составлении программы на более высоком уровне, работая с уже готовыми элементами, выполняющими различные действия. Кроссплатформенность. Программа, написанная на Python, будет функционировать совершенно одинаково вне зависимости от того, в какой операционной системе она запущена. Отличия возникают лишь в редких случаях, и их легко заранее предусмотреть благодаря наличию подробной документации. Изучение языка программирования python - это просто и понятно даже для чайников. Уже сейчас вы можете скачать python и написать свою первую программу!
  3. 10 интересных фактов про Python Язык Python известен уже множество лет и за это время обрел не мало интересных фактов. Мы расскажем 10 особенностей, за которые все любят Питон. Python является языком программирования, который относительно просто освоить, поэтому он пользуется популярностью у начинающих программистов. Если всё ещё не можете решиться начать карьеру с Python, мы подобрали 10 важных причин, чтобы развеять любые сомнения и помочь сделать правильный выбор. 1. Мультиплатформенность Сложно найти среду, в которой невозможно было бы использовать Python. Он применяется для написания программ на карманные компьютеры, смартфоны и т. д. вплоть до серверных сетей. Уже сегодня язык поддерживает работы с Windows, Linux, macOS, в том числе с версией X. Также работает с мобильной Windows, Android, Symbian, iPhone OS от версии 2.0, Palm OS, OS/2, OS/390 и OS/400. 2. Хорошее прошлое Многие говорят о Python, как о молодом языке. Это очень относительно, зависит от тог, с чем сравнивать. Если сравнить с C, действительно Python является молодым языком. В целом его развитие началось в конце 80-х годов, впервые он увидел мир в полноценной версии в 1991 году. В этом году языку исполнилось 27 лет – это вполне зрелый возраст, за это время он успел приобрести необходимые доработки и был качественно оптимизирован. Его точно нельзя назвать мёртвым, так как регулярно выходят обновления. 3. Крупные компании любят и используют Python Подразумевается взаимодействие с крупнейшими компаниями мира: NASA, Google, Yahoo, Microsoft и т. д. применяют язык для своих разработок. Другие корпорации, использующие Python можно найти в сети. Google акцентирует внимание на Java, C++ и Python. Microsoft вовсе открыла центр разработчиков Python. 4. Популярность Общепризнанный индекс TIOBE даёт Python 3 место по популярности. Если присмотреться к рейтингу, здесь 2 место занимает язык C, его можно заменить на одну единицу, тогда вовсе Python получит второе место, первое за Java. 5. Не все так просто с названием Название языка произошло от телевизионного шоу – «Летающий цирк Монти Пайтона». Это заявление разработчика Гвидо ван Россум опровергает домыслы о неприятном происхождении названия. Этот факт занесён в FAQ, только его никто не читает. 6. Yahoo обожает Python Посмотреть возможности языка можно на примере программных продуктов корпорации Yahoo: TurboGears, Django, Zope. 7. Взаимодействие с .NET и Java Программисты легко интегрируют Python с объектами .NET, COM и CORBA. Если вы Java-программист, есть возможность использовать Jython – это версия языка Python для JVM. Для .NET разработчиков есть Python for .NET, в качестве альтернативы – IronPython. 8. «Батарейка» внутри Лёгкое начало работы с языком и возможность реализации проектов всех уровней сложности обеспечивает «батарейка». На сленге разработчиков Python – это огромная библиотека. В ней присутствуют все данные об асинхронной обработке запросов, взаимодействии с ZIP-архивами и т. д. 9. Python работает с ICE ICE – это система объектов, которая используется механизм RPC. Разработчики знакомые с CORBA заметят подобие в ICE, но здесь удалось сделать всё значительно проще и компактнее. В любом случае Python работает с обеими технологиями. 10. Легкий при изучении Язык весьма простой для изучения, даже без опыта разобраться в нём будет несложно. Весьма часто обучаться программированию рекомендуют с этого языка. Не исключено, что Python займёт место ранее известного Turbo Pascal. Существует много способов получения знаний – обилие книг, множество профессионалов в округе, полноценная библиотека. Изучение Python - это лёгкий способ получить новую профессию и высокооплачиваемую работу.
  4. 5 особенностей языка Java, о которых вы должны знать О некоторых особенностях языка Java порой не знают сами джависты. Рассказываем о 5 особенностях этого языка, которые должен знать каждый. Инструкции можно переставлять Рассмотрим пример: int x = 1; int y = 3; System.out.println(x + y); Взглянув на код, мы можем предположить, что сначала значение 1 будет присвоено x, а затем значение 3 – переменной y. Но если порядок присвоения переменных изменится, конечный результат окажется тем же: мы увидим в выводе 4. Используя это наблюдение, компилятор может безопасно изменить порядок этих двух операций присваивания в случае необходимости. Когда мы компилируем код, компилятор делает именно это: он может свободно переупорядочивать инструкции, если это не изменяет ожидаемое поведение системы. И хотя перестановка в примере выше не приведет ни к каким положительным изменениям, есть ситуации, когда изменение порядка инструкций повысит производительность. Предположим, в нашем коде переменные x и y дважды увеличиваются в процессе чередования. int x = 1; int y = 3; x++; y++; x++; y++; System.out.println(x + y); Этот код должен вывести 8, и он сделает это даже при изменении порядка операций. В процессе оптимизации компилятор может полностью избавить код от операций приращения: // Смена порядка инструкций int x = 1; int y = 3; x++; x++; y++; y++; System.out.println(x + y); // Сжатие инструкций int x = 1; int y = 3; x += 2; y += 2; System.out.println(x + y); // Полное сжатие инструкций int x = 3; int y = 5; System.out.println(x + y); В действительности, компилятор продвинется еще на шаг дальше и переместит значения x и y сразу в print. В числовых значениях можно использовать нижние подчеркивания Читать большие числа неудобно. Для удобства чтения в математике их принято разделять точкой: вместо 1183548876845 получается вполне читабельное 1.183.548.876.845. К сожалению, в коде языка Java часто встречаются огромные числа в виде констант, как в примере ниже. public class Foo { public static final long LARGE_FOO = 1183548876845l; } System.out.println(LARGE_FOO); Конечно, содержимое констант редко претендует на эстетичность, но разбирать такое число глазами не очень удобно. К счастью, в Java Development Kit (JDK) седьмой версии появилась возможность разделять числа с помощью нижнего подчеркивания ( _ ) так же, как мы привыкли делать это с помощью точки. public class Foo { public static final long LARGE_FOO = 1_183_548_876_845l; } System.out.println(LARGE_FOO); Таким же образом, кстати, можно разделять и длинные хвосты десятичных дробей. public static final double LARGE_BAR = 1.546_674_876; Автоупаковка целочисленных кэшируется В Java мы можем записать целочисленное значение следующим образом: Integer myInt = 500; Примитив int 500 преобразуется в объект типа Integer и сохраняется в myInt. Такая обработка называется автобоксинг или автоупаковка, поскольку преобразование примитива в объект типа Integer происходит автоматически. Так как myInt является объектом типа Integer, мы ожидаем, что сравнение его с другим объектом того же типа и содержащего то же значение с помощью оператора == приведет к false. Но вызов сравнения для этих двух объектов приведет к true, так как оба объекта представлены одним значением – 500: Integer myInt = 500; Integer otherInt = 500; System.out.println(myInt == otherInt); // false System.out.println(myInt.equals(otherInt)); // true На этом этапе autoboxing работает точно так, как ожидается, но что произойдет, если мы попробуем это с меньшим числом? Например, 25: Integer myInt = 25; Integer otherInt = 25; System.out.println(myInt == otherInt); // true System.out.println(myInt.equals(otherInt)); // true Тут мы увидим, что оба объекта равны как по ==, так и по equals. Это означает, что два объекта Integer, фактически являются одним объектом. Такое странное поведение, на самом деле, не является ни ошибкой, ни недосмотром: оно было допущено умышленно. Поскольку многие операции автобоксинга выполняются с небольшими числами (до 127), JLS говорит о том, что значения Integer в диапазоне от -128 до 127 включительно кэшируются. Файлы языка Java могут содержать множество не вложенных классов В одном файле исходного кода может разместиться множество не вложенных классов, которые не являются public. Для них уровень доступа будет установлен как package-private (то есть, без модификатора доступа). При этом, в каждом файле может содержаться только один public класс. public class Foo { //... } class Bar { //... } class Bar1 { //... } После компиляции количество файлов будет равно количеству не вложенных классов. StringBuilder используется для конкатенации строк Конкатенация строк присутствует почти во всех языках программирования и позволяет соединять между собой строки или объекты и примитивы разных типов в одну строку. Одна из сложностей конкатенации в Java – неизменяемость строк. То есть мы не можем все время добавлять данные к одной и той же строке. Каждый append будет создавать новый объект String и продолжать работу с ним. Это не критично, когда нужно произвести конкатенацию нескольких строк, но когда объемы вырастают до, скажем, 1000 строк, эта техника становится нестабильной, да и создавать тысячу сущностей String довольно расточительно. Однако документация JLS сообщает, что подобного можно избежать. Для этого нужно использовать StringBuilder, который поработает как буфер. Строка, к которой идет присоединение других строк, будет существовать до тех пор, пока она нужна. Таким образом, при конкатенации будет существовать всего одна сущность String. Для примера, соединение строк с помощью StringBuilder в цикле выглядит так: StringBuilder builder = new StringBuilder(); for (int i = 0; i < 1000; i++) { builder.append("a"); } String myString = builder.toString();
  5. Разбираемся с тем, как устроены функции в Java и Kotlin Изучаете Java и Kotlin? Тогда вам будет полезно узнать о различных типах синхронизации, блокировки и обеспечения безопасности потоков. Что такое синхронизация? В многопоточном мире нам необходим доступ к объектам между потоками, при отсутствии синхронизации могут возникнуть проблемы. Вот базовый пример, который объясняет, почему это нужно: import kotlinx.coroutines.* fun main() = runBlocking { var sharedCounter = 0 val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for(i in 1..1000){ sharedCounter++ } } } coroutines.forEach { corotuine-> corotuine.join() } }.join() println("The number of shared counter should be 10000000, but actually is $sharedCounter") } Здесь мы запускаем 1000 корутинов (можете считать их легковесными потоками) в 4 потоках, и каждый из них увеличивает величину sharedCounter 1 000 раз, так, чтобы в итоге оно стало равным 1 000 000. Шансов, что у вас получится это сделать, практически нет. Прежде чем мы объясним происходящее, представьте, что есть одна комната для размышлений (counter) и очередь из людей (threads), где каждый хочет зайти внутрь, при этом, она рассчитана только на одного человека. В этой комнате есть дверь, которую закрывают, если кто-то уже внутри. Но есть проблема − прямо сейчас эту дверь можно открыть снаружи, потому что в ней нет замка. В приведенном выше коде для увеличения значения sharedCounter каждый поток пытается сделать следующее: Получить текущее значение. Сохранить его во временной переменной и увеличьте временную переменную на 1. Сохранить значение временной переменной в sharedCounter. Но что если пока один поток получает текущее значение, подключится другой и тоже попытается получить текущее значение? Они оба получат одно и то же значение. Таким образом, каждый из них увеличивает это значение на 1 и сохраняет его. Эта проблема может возникать и в других ситуациях: например, когда поток проходит второй шаг, другие потоки увеличивают и сохраняют значение sharedCounter, а при переходе на следующий шаг, первый поток сохраняет неактуальную версию. Тогда итоговое значение будет не таким, каким должно. Чтобы решить эту проблему, нужно синхронизировать потоки для работы над этим значением. Далее рассказываем, как это сделать. Volatile В Java и Kotlin есть ключевое слово volatile (в Kotline в виде аннотации @volatile), которые применяются к полям и гарантируют, что считываемое значение поступает из основной памяти, а не из кэша процессора, поэтому все участники процесса будут ожидать окончания параллельной записи, прежде чем считать значение. import kotlinx.coroutines.* @Volatile var sharedCounter: Int = 0 fun main() = runBlocking { val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for (i in 1..1000) { sharedCounter++ } } } coroutines.forEach { corotuine -> corotuine.join() } }.join() println("The number of shared counter is $sharedCounter") } Как видно из кода, в нашем сценарии volatiles не помогают, потому что volatile не спасает от проблемы считывания устаревшего значения (между чтением переменной и записью нового значения в одном потоке может произойти действие в другом). Дело в том, что ++ здесь − не атомарная операция. Volatiles используются для создания потоко-безопасных синглетонов в Java путем двойной проверки экземпляра синглтона. Подробнее об этих методах написано здесь. Synchronized Одним из решений является использование synchronized из Java. В Java и Kotlin есть два типа синхронизации − синхронизированные методы и синхронизированные блоки. Для понимания − синхронизированные методы обозначается, как synchronized в Java и @Synchronized в Kotlin. Чтобы использовать это решение, мы можем перенести увеличение счётчика в отдельную функцию, помеченную как synchronized. import kotlinx.coroutines.* var sharedCounter = 0 @Synchronized fun updateCounter(){ sharedCounter++ } fun main() = runBlocking { val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for(i in 1..1000){ updateCounter() } } } coroutines.forEach { corotuine-> corotuine.join() } }.join() println("The number of shared counter is $sharedCounter") } Как видим, выходное значение верное. Если провести аналогию с комнатами, то @Synchronizedбудет являться замком на двери, который открывается единственным существующим ключом. Один человек (поток) берёт ключ, заходит, закрывается изнутри. Никто другой не может зайти в этот момент. Только после того, как ключ вернут на место, это станет возможным. Именно так работает synchronized в Java и Kotlin. Следует отметить ещё один момент, который касается синхронизированных методов: доступ ко всему методу ограничен, даже если происходят промежуточные действия, не требующие синхронизации. Например, при использовании synchronized, updateCounter синхронизируется даже тогда, когда sharedCounter не обновляется. Синхронизация может происходить и на более низком уровне, например, с помощью синхронизированных блоков. Давайте попробуем увеличивать число только тогда, когда наш номер итерации четный. import kotlinx.coroutines.* class Incrementor() { var sharedCounter: Int = 0 private set fun updateCounterIfNecessary(shouldIActuallyIncrement: Boolean) { if (shouldIActuallyIncrement) { synchronized(this) { sharedCounter++ } } } } fun main() = runBlocking { val incrementor = Incrementor() val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for (i in 1..1000) { incrementor.updateCounterIfNecessary(it % 2 == 0) } } } coroutines.forEach { corotuine -> corotuine.join() } }.join() println("The number of shared counter is ${incrementor.sharedCounter}") } Синхронизированные блоки хороши тем, что позволяют использовать любой объект в качестве замка. В приведенном выше коде, synchronized определяет this (экземпляр Instance) как объект блокировки. Любой поток, который достигает этой точки, блокирует Incrementor, выполняет работу в блоке и снимает блокировку synchronized. Атомарные примитивы Атомарные примитивы позволяют синхронизировать действия над переменными базовых типов. Например, в примере ниже класс AtomicIntegerпредоставляет свою реализацию функции ++. import kotlinx.coroutines.* import java.util.concurrent.atomic.AtomicInteger class Incrementor() { val sharedCounter: AtomicInteger = AtomicInteger(0) fun updateCounterIfNecessary(shouldIActuallyIncrement: Boolean) { if (shouldIActuallyIncrement) { sharedCounter.incrementAndGet() } } fun getSharedCounter():Int { return sharedCounter.get() } } fun main() = runBlocking { val incrementor = Incrementor() val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for (i in 1..1000) { incrementor.updateCounterIfNecessary(it % 2 == 0) } } } coroutines.forEach { corotuine -> corotuine.join() } }.join() println("The number of shared counter is ${incrementor.getSharedCounter()}") } Lock Класс Lock выполняет примерно те же функции, что и synchronized, только более гибко. synchronizedдаёт возможность синхронизировать блоки кода, тогда как с Lock можно реализовывать более сложную логику. Примерно так: import kotlinx.coroutines.* import java.util.concurrent.locks.ReentrantLock class Incrementor() { private val sharedCounterLock = ReentrantLock() var sharedCounter: Int = 0 private set fun updateCounterIfNecessary(shouldIActuallyIncrement: Boolean) { if (shouldIActuallyIncrement) { try { sharedCounterLock.lock() sharedCounter++ } finally { sharedCounterLock.unlock() } } } } fun main() = runBlocking { val incrementor = Incrementor() val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for (i in 1..1000) { incrementor.updateCounterIfNecessary(it % 2 == 0) } } } coroutines.forEach { corotuine -> corotuine.join() } }.join() println("The number of shared counter is ${incrementor.sharedCounter}") } Благодаря «замкам» можно, например, организовать последовательную блокировку-разблокировку объектов LinkedList или ввести иерархическую блокировку. Взаимные блокировки − большая область для отдельного изучения. Кроме того, классы Lock предоставляют удобные функции, например Lock.tryLock(long, TimeUnit), которая попытается получить блокировку над объектом, но не будет ожидать вечность. Средства параллелизма Существуют также и другие классы для обеспечения параллельности и синхронизации. Рассмотрим на примерах. 1. Semaphore Semaphore, как и замки, могут быть использованы для синхронизации. Lock (можно рассматривать его как мьютекс) используется, когда нужно что-то сделать линейным и атомарным способом. Semaphore принимают общее количество разрешений в своем конструкторе, которое может использоваться для ограничения одновременного доступа к ресурсу. Они используются тогда, когда требуются сигналы. Существует некоторый паттерн: producer − consumer. Одни потоки ведут запись и подают об этом сигнал. Другие следят за состоянием сигналов, только чтобы осуществлять чтение данных. Посмотрите на пример кода ниже: сейчас мы адаптируем его так, чтобы можно было использовать semaphore. Хотя в данном случае можно найти и более удачные способы обеспечения синхронизации. import kotlinx.coroutines.* import java.util.concurrent.Semaphore class Incrementor() { private val sharedCounterLock = Semaphore(1) var sharedCounter: Int = 0 private set fun updateCounterIfNecessary(shouldIActuallyIncrement: Boolean) { if (shouldIActuallyIncrement) { try { sharedCounterLock.acquire() sharedCounter++ } finally { sharedCounterLock.release() } } } } fun main() = runBlocking { val incrementor = Incrementor() val scope = CoroutineScope(newFixedThreadPoolContext(4, "synchronizationPool")) scope.launch { val coroutines = 1.rangeTo(1000).map { launch { for (i in 1..1000) { incrementor.updateCounterIfNecessary(it % 2 == 0) } } } coroutines.forEach { corotuine -> corotuine.join() } }.join() println("The number of shared counter is ${incrementor.sharedCounter}") } Ещё, функция semaphore обеспечивает справедливую очередность исполнения потоков, если задать второй параметр в конструкторе − fair. Тогда поток, ожидающий очереди дольше всего, получит доступ первым. То есть, поддерживается FIFO очередь. Однако использование tryAcquireнапрямую не сохраняет эту «честность». Если требуется соблюдение правила FIFO, можно писать tryAcquire(0, TimeUnit.SECONDS). 2. CyclicBarrier CyclicBarrier используется, когда есть фиксированное количество участников − потоков или методов, ожидающих окончания выполнения процесса каждым из участников для продолжения работы. Представим, что у нас есть несколько исполнителей, генерирующих по одному числу, и мы хотим суммировать все числа после окончания работы. import java.util.* import java.util.concurrent.CyclicBarrier fun main() { val createdValues = mutableListOf<Int>() /** * create a cyclic barrier that waits for 5 threads to finish their jobs, and after that, * prints the sum of all the values. */ val cyclicBarrier = CyclicBarrier(3) { println("Sum of all values is ${createdValues.sum()}") } val threads = 1.rangeTo(3).map { number -> Thread { Thread.sleep(Random().nextInt(500).toLong()) createdValues.add(number) cyclicBarrier.await() println("I am thread ${Thread.currentThread().name} and I finished my Job!") }.apply { start() } } threads.forEach { thread -> thread.join() } } В приведенном примере, блок кода, который был передан конструктору CyclicBarrier, запускается после выполнения 3 потоками await(). После выполнения действия CyclicBarrier потоки продолжают работать с тех мест, где остановились. Еще один момент: createdValues не является потоко-безопасным, и для параллельной работы его следует заменить на synchronizedList, речь о котором пойдёт далее. Если пара потоков попытается одновременно добавить новый элемент в список, то один из них может быть утерян! 3. Параллельные коллекции Существует несколько способов обработки синхронизированных коллекций в Java. Collections.synchronizedList(List<K>) является классом, который может использоваться для выполнения регулярных операций со списками (add, addAll, get, set, ...) методом синхронизации. Существуют также аналогичные реализации Map, Set и другие. CopyOnWriteArrayList может использоваться для обеспечения поточно-ориентированных операций модификации в List. Любая операция изменения сначала копирует весь список в локальную переменную с помощью итератора, выполняет действие, а затем заменяет его исходным списком. Эта коллекция примечательна тем, что обход списка не требует синхронизации. HashTable обеспечивает синхронизированный доступ к Map. Следует отметить, что синхронизируются только отдельные функции Map. Например, put не в их числе, поэтому безопасность потока на всей карте не гарантируется. ConcurrentHashMap<K,V> можно использовать для обеспечения безопасности потоков во всех методах HashMap. Важно отметить, что операции чтения (например, get) не блокируют Map целиком. Deadlock Код ниже не работает, а точнее никогда не остановится. Догадаетесь почему? data class Human(val name:String) { @Synchronized fun sayHi(to: Human){ println("$name saying hi to ${to.name}") Thread.sleep(500) to.sayHiBack(this) } @Synchronized fun sayHiBack(to: Human){ println("$name saying hi back to ${to.name}") } } fun main() { val adam = Human("adam") val eve = Human("eve") val adamThread = Thread { adam.sayHi(eve) }.apply { start() } val eveThread = Thread { eve.sayHi(adam) }.apply { start() } adamThread.join() eveThread.join() } У нас есть два человека − Адам и Ева. Когда один говорит другому привет, второй вынужден ответить ему тем же. Когда поток Адама запускается, он пытается сказать «привет» Еве, блокируя собственный объект с помощью @Synchronized и ждёт 500 мс. Тем временем начинается поток Евы, по той же схеме. По истечении 500мс, Адам вызывает Eve.sayHiBack, то есть пытается получить блокировку ещё и над объектом eve. Ева также ждёт освобождения доступа к adam.sayHiBack. Таким образом, оба потока просто останавливаются в бесконечном ожидании. При написании синхронизированного кода нужно подумать обо всех возможных ситуациях в блокировках-разблокировках. Вы можете создать такую же взаимную блокировку, как в примере выше, используя другие средства синхронизации, замки. Заключение В этой статье были рассмотрены различные методы синхронизации и обеспечения потокобезопасности, коллекции для параллельной работы и вспомогательные классы Java и Kotlin.
  6. Крутые модули Python, которые вы не используете, а надо бы Какие методы и модули Python сделают ваш код чище и эффективнее? Рассмотрим 5 скрытых жемчужин стандартной библиотеки Python. Язык Python – прекрасен, и содержит много встроенных модулей, которые помогают сделать код более качественным и красивым. Задача В этой статье будем использовать некоторые малоизвестные модули и методы, с помощью которых можно улучшить программирование на Python. Как с точки зрения наглядности, так и производительности. NamedTuple Полагаем, некоторые из вас уже знакомы с более популярным именованным кортежем namedtupleиз модуля collections (если нет – ознакомьтесь). Но начиная с Python 3.6, в модуле typing доступен новый класс: NamedTuple. Оба предназначены для быстрого создания читаемых неизменяемых объектов. NamedTuple – на самом деле типизированная версия namedtuple и, по мнению разработчика, этот класс гораздо более читаемый: In [2]: import typing In [3]: class BetterLookingArticle(typing.NamedTuple): ...: title: str ...: id: int ...: description: str = "No description given." ...: In [4]: BetterLookingArticle(title="Python is cool.", id=1) BetterLookingArticle(title='Python is cool.', id=1, description='No description given.') Вот альтернатива c использованием namedtuple: In [6]: import collections In [7]: Article = collections.namedtuple("Article", ["title", "description", "id"]) In [8]: Article(title="Python is cool.", id=1, description="") Article(title='Python is cool.', description='', id=1) array.array Эффективные массивы числовых значений. Массивы относятся к типу последовательностей и ведут себя как списки, за исключением того, что тип хранящихся в них объектов ограничен. — документация Python Когда используется модуль array, создание массива происходит с указанием типа данных, который будут использовать все его элементы. Давайте сравним время выполнения кода с обычным списком, записав много целых чисел в файл (используя модуль pickle для обычного списка): https://gist.github.com/AdamGold/961758c66cdfe92642eabb61d9ce9866 В 14 раз быстрее. Это много. Конечно, время выполнения также зависит и от модуля pickle, но всё же массив гораздо компактнее, чем список. Поэтому, если используете простые числовые значения, рассматривайте использование модуля array. itertools.combinations itertools – впечатляющий модуль. У него так много разных методов, позволяющих экономить время. Есть даже репозиторий GitHub, содержащий ещё больше подобных инструментов. Давайте посмотрим на метод combinations. Он принимает в качестве аргументов итерируемый объект и целое число. В результате получаем генератор, состоящий из всех возможных комбинаций итерируемого объекта. Максимальная длина последовательности равна указанному целому числу. Сочетания не дублируются: In [16]: import itertools In [17]: list(itertools.combinations([1, 2, 3, 4], 2)) [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)] dict.fromkeys Быстрый и красивый способ создания словаря со значениями по умолчанию: In [18]: dict.fromkeys(["key1", "key2", "key3"], "DEFAULT_VALUE") {'key1': 'DEFAULT_VALUE', 'key2': 'DEFAULT_VALUE', 'key3': 'DEFAULT_VALUE'} Последний, но не менее важный – модуль dis Модуль dis обеспечивает анализ байт-кодаCPython путём его дизассемблирования. Как вы наверняка знаете, Python компилирует исходный код в набор инструкций под названием «байт-код». Модуль dis помогает обрабатывать эти инструкции. И это отличный инструмент для отладки. Вот пример из книги «Fluent Python»: In [22]: t = (1, 2, [3, 4]) In [23]: t[2] += [30, 40] --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-25-af836a8d44a2> in <module> ----> 1 t[2] += [30, 40] TypeError: 'tuple' object does not support item assignment In [24]: t Out[24]: (1, 2, [3, 4, 30, 40]) Получили ошибку, но операция всё равно завершилась. Как так? Узнаем, если посмотрим на байт-код (добавлены комментарии рядом с важными частями): In [25]: dis.dis("t[a] += b") 1 0 LOAD_NAME 0 (t) 2 LOAD_NAME 1 (a) 4 DUP_TOP_TWO 6 BINARY_SUBSCR 8 LOAD_NAME 2 (b) 10 INPLACE_ADD --> (value in t[a]) += b --> выполняется, так как список изменяемый 12 ROT_THREE 14 STORE_SUBSCR --> Assign t[a] = our list --> Ошибка, t[a] неизменяемый. 16 LOAD_CONST 0 (None) 18 RETURN_VALUE
  7. ТОП-6 алгоритмов сортировки на Java для новичков Изучение алгоритмов сортировки на языке Java поможет не изобретать велосипеды и быстро выскочить на лесенку карьерного роста. Задействование алгоритмов сортировки поможет нам упорядочить массивы Java. Для понимания: сортировка чисел от наименьшего к большему или наоборот, а также лексикографический порядок – это примеры алгоритмов сортировки, способные упорядочить Java строки и числа Сортировка пузырьком Слышали о сортировке пузырьком? Его популярность обусловлена простотой, наглядностью и, конечно, названием. Алгоритм просматривает массив и сравнивает каждую пару соседних элементов. Когда он встречает пару элементов, расположенных не по порядку, происходит замена двух элементов местами. Остается вопрос: как узнать, что все элементы упорядочены? В этом случае очередная итерация пройдет без замены соседних элементов. Вот шаги для сортировки массива чисел от наименьшего к большему: 4 2 1 5 3: два первых элемента расположены в массиве в неверном порядке. Меняем их. 2 4 1 5 3: вторая пара элементов тоже «не в порядке». Меняем и их. 2 1 4 5 3: а эти два элемента в верном порядке (4 < 5), поэтому оставляем как есть. 2 1 4 5 3: очередная замена. 2 1 4 3 5: результат после одной итерации. Для полной сортировки нужен еще один шаг. Третья итерация пройдет уже без замены. Так вы поймете, что массив отсортирован. Но причём тут пузырьки? Посмотрите снова на пример, и вы увидите, что алгоритм как бы смещается вправо. По этому поведению элементов в массиве и возникла аналогия с «пузырьками», всплывающими на «поверхность». Реализация Функция входит в цикл while, в котором проходит весь массив и меняет элементы местами при необходимости. Массив в алгоритме считается отсортированным. При первой замене доказывается обратное и запускается еще одна итерация. Цикл останавливается, когда все пары элементов в массиве пропускаются без замен: public static void bubbleSort(int[] array) { boolean sorted = false; int temp; while(!sorted) { sorted = true; for (int i = 0; i < array.length - 1; i++) { if (array > array[i+1]) { temp = array; array = array[i+1]; array[i+1] = temp; sorted = false; } } } } Будьте осторожны с формулировкой условия замены! Например, при условии a >= a[i+1]алгоритм войдет в бесконечный цикл, потому что для равных элементов это условие остается true: отсюда следует бесконечная замена Временная сложность Рассмотрим наихудший сценарий. Вот в чем вопрос: сколько итераций нужно для сортировки всего массива? Пример: 5 4 3 2 1 При первой итерации 5 «всплывает на поверхность», при этом остальные элементы остаются в порядке убывания. Если вы хотите получить отсортированный массив, придется делать по одной итерации для каждого элемента, кроме 1, и еще одну итерацию для проверки, что в сумме составляет 5 итераций. Расширьте это утверждение для массива из nэлементов, и получите n итераций. В коде это означает, что цикл while будет запускаться максимум n раз. Каждая n-ая итерация по всему массиву (цикл for в коде) означает, что временная сложность в наихудшем случае будет равна O(n ^ 2). Сортировка вставками Этот алгоритм разделяет оригинальный массив на сортированный и несортированный подмассивы. Длина сортированной части равна 1 в начале и соответствует первому (левому) элементу в массиве. После этого остается итерировать массив и расширять отсортированную часть массива одним элементом с каждой новой итерацией. После расширения новый элемент помещается на свое место в отсортированном подмассиве. Это происходит путём сдвига всех элементов вправо, пока не встретится элемент, который не нужно двигать. В приведенном ниже массиве жирная часть отсортирована в порядке возрастания. Посмотрите что произойдет в этом случае: 3 5 7 8 4 2 1 9 6: выбираем 4 и помним, что это элемент, который нужно вставить. 8 > 4, поэтому сдвигаем. 3 5 7 x 8 2 1 9 6: здесь x – нерешающее значение, так как элемент будет перезаписан (на 4, если это подходящее место, или на 7, если смещение). 7 > 4, поэтому сдвигаемся. 3 5 x 7 8 2 1 9 6 3 x 5 7 8 2 1 9 6 3 4 5 7 8 2 1 9 6 Теперь вы видите, что отсортированная часть дополнилась элементом. Каждая следующая итерация делает то же самое, и к концу вы получите отсортированный массив! Реализация public static void insertionSort(int[] array) { for (int i = 1; i < array.length; i++) { int current = array; int j = i - 1; while(j >= 0 && current < array[j]) { array[j+1] = array[j]; j--; } // в этой точке мы вышли, так что j так же -1 // или в первом элементе, где текущий >= a[j] array[j+1] = current; } } Временная сложность Вернемся к худшему сценарию – к массиву, отсортированному в убывающем порядке. В этом случае каждая итерация сдвигает отсортированный массив на единицу O(n). Придется делать это для каждого элемента в каждом массиве, что приведет к сложности равной O(n ^ 2). Сортировка выбором Сортировка выбором тоже разделяет массив на сортированный и несортированный подмассивы. Но на этот раз сортированный подмассив формируется вставкой минимального элемента не отсортированного подмассива в конец сортированного, заменой: 3 5 1 2 4 1 5 3 2 4 1 2 3 5 4 1 2 3 5 4 1 2 3 4 5 1 2 3 4 5 Реализация В каждой итерации вы предполагаете, что первый неотсортированный элемент минимален и итерируете по всем оставшимся элементам в поисках меньшего. После нахождения текущего минимума неотсортированной части массива вы меняете его местами с первым элементом, и он уже часть отсортированного массива: public static void selectionSort(int[] array) { for (int i = 0; i < array.length; i++) { int min = array; int minId = i; for (int j = i+1; j < array.length; j++) { if (array[j] < min) { min = array[j]; minId = j; } } // замена int temp = array; array = min; array[minId] = temp; } } Временная сложность При поиске минимума для длины массива проверяются все элементы, поэтому сложность равна O(n). Поиск минимума для каждого элемента массива равен O(n^2). Сортировка слиянием Сортировка слиянием эффективнее, чем примеры алгоритмов сортировки, представленные выше, благодаря использованию рекурсии и подходу «разделяй и властвуй». Массив делится на два подмассива, а затем происходит: Сортировка левой половины массива (рекурсивно) Сортировка правой половины массива (рекурсивно) Слияние На схеме показана работа рекурсивных вызовов. Для массивов, отмеченных стрелкой вниз, вызывается функция. Во время слияния возвращаются массивы со стрелкой вверх. Всё просто: мы следуем за стрелкой вниз к нижней части дерева, а затем возвращаемся и объединяем. В примере массив 3 5 4 2 1 делится на 3 5 4и 2 1 и так далее. При достижении «дна» начинается объединение и сортировка. Реализация В главную функцию передаются left и right – индексы подмассивов для сортировки, крайние слева и справа. Изначально они имеют значения 0и array.length-1, в зависимости от реализации. Основа нашей рекурсии гарантирует, что мы выйдем, когда закончим, или когда left и rightвстретятся друг с другом. Мы находим среднюю точку mid и рекурсивно сортируем подмассивы слева и справа от середины, в итоге объединяя наши решения. Возможно, вы вспомните дерево и спросите: почему мы не передаем два меньших массива? Ответ прост: это не нужно и вызовет огромное потребление памяти для очень длинных массивов. Достаточно следовать индексам не нарушая логики дерева рекурсии: public static void mergeSort(int[] array, int left, int right) { if (right <= left) return; int mid = (left+right)/2; mergeSort(array, left, mid); mergeSort(array, mid+1, right); merge(array, left, mid, right); } Для сортировки двух подмассивов в один нужно вычислить их длину и создать временные массивы, в которые будем копировать. Так можно свободно изменять главный массив. После копирования мы проходим по результирующему массиву и назначаем текущий минимум. Помните, что наши подмассивы отсортированы? Теперь нужно просто выбрать наименьший из двух элементов, которые еще не были выбраны, и двигать итератор для этого массива вперед: void merge(int[] array, int left, int mid, int right) { // вычисляем длину int lengthLeft = mid - left + 1; int lengthRight = right - mid; // создаем временные подмассивы int leftArray[] = new int [lengthLeft]; int rightArray[] = new int [lengthRight]; // копируем отсортированные массивы во временные for (int i = 0; i < lengthLeft; i++) leftArray = array[left+i]; for (int i = 0; i < lengthRight; i++) rightArray = array[mid+i+1]; // итераторы содержат текущий индекс временного подмассива int leftIndex = 0; int rightIndex = 0; // копируем из leftArray и rightArray обратно в массив for (int i = left; i < right + 1; i++) { // если остаются нескопированные элементы в R и L, копируем минимальный if (leftIndex < lengthLeft && rightIndex < lengthRight) { if (leftArray[leftIndex] < rightArray[rightIndex]) { array = leftArray[leftIndex]; leftIndex++; } else { array = rightArray[rightIndex]; rightIndex++; } } // если все элементы были скопированы из rightArray, скопировать остальные из leftArray else if (leftIndex < lengthLeft) { array = leftArray[leftIndex]; leftIndex++; } // если все элементы были скопированы из leftArray, скопировать остальные из rightArray else if (rightIndex < lengthRight) { array = rightArray[rightIndex]; rightIndex++; } } } Временная сложность Хотите легко рассчитывать рекурсивные реализации алгоритмов сортировки? Приготовьтесь к математике :) Для вычисления временной сложности нам понадобится основная теорема о рекуррентных соотношениях. Временную сложность рекурсивных алгоритмов сортировки можно описать следующим уравнением: Здесь a – это количество меньших рекурсивных вызовов, на которые мы делим проблему, а bуказывает на входную величину рекурсивных вызовов. Остальная часть уравнения – это сложность слияния всех решений в одно конечное. Упомянутая теорема решит все за вас: Если T(n) – это время выполнения алгоритма для сортировки массива длинной n, сортировка слиянием запустится дважды для массивов длиной вполовину от оригинального. Так, если a=2, b=2, шаг слияния занимает O(n)памяти при k=1. Это означает, что уравнение для сортировки слиянием будет выглядеть так: Примените теорему, и вы увидите, что в нашем случае a=b^k, ибо 2=2^1. Значит, сложность равна O(nlog n), и это лучшая временная сложность для алгоритма сортировки. Доказано, что массив не может быть отсортирован быстрее, чем O(nlog n). Пирамидальная сортировка Для понимания работы пирамидального алгоритма сортировки нужно понять структуру, на которой он основан – пирамиду. Пирамида или двоичная куча – это дерево, в котором каждый узел состоит в отношениях с дочерними узлами. Добавление нового узла начинается с левой позиции нижнего неполного уровня. По мере движения вниз по дереву значения уменьшаются (min-heap) или увеличиваются (max-heap). Смотрите пример max-heap: А теперь представим пирамиду в виде массива: 8 5 6 3 1 2 4 Чтение графа сверху вниз здесь представлено слева направо. Мы добились того, что позиция дочернего элемента по отношению к k-ому элементу в массиве – 2\*k+1 и 2\*k+2 (при условии, что индексация начинается с 0). Проверьте сами! И наоборот, для k-го элемента дочерняя позиция всегда равна (k-1)/2. С этими знаниями вы сделаете max-heap из любого массива! Для этого проверьте каждый элемент на условие, что каждый из его дочерних элементов имеет меньшее значение. Условие верно? Тогда меняйте местами один из дочерних элементов с родительским и повторяйте рекурсию с новым родительским элементом (он может всё ещё быть больше другого дочернего). 6 1 8 3 5 2 4: оба дочерних меньше родительского, оставляем как есть. 6 1 8 3 5 2 4: 5 > 1, поэтому меняем их. Теперь рекурсивно проверяем 5. 6 5 8 3 1 2 4: оба дочерних меньше 5, поэтому пропускаем. 6 5 8 3 1 2 4: 8 > 6, поэтому меняем их. 8 5 6 3 1 2 4: мы получили пирамиду, изображенную выше! Вы научились строить пирамиду из массива, все остальное гораздо проще! Поменяйте местами корень пирамиды с концом массива, и сократите массив на единицу. Постройте кучу из сокращенного массива и повторяйте процесс: 8 5 6 3 1 2 4 4 5 6 3 1 2 8: замена 6 5 4 3 1 2 8: сортировка 2 5 4 3 1 6 8: замена 5 2 4 2 1 6 8: сортировка 1 2 4 2 5 6 8: замена И так далее. Видите закономерность? Реализация static void heapify(int[] array, int length, int i) { int leftChild = 2*i+1; int rightChild = 2*i+2; int largest = i; // если левый дочерний больше родительского if (leftChild < length && array[leftChild] > array[largest]) { largest = leftChild; } // если правый дочерний больше родительского if (rightChild < length && array[rightChild] > array[largest]) { largest = rightChild; } // если должна произойти замена if (largest != i) { int temp = array; array = array[largest]; array[largest] = temp; heapify(array, length, largest); } } public static void heapSort(int[] array) { if (array.length == 0) return; // Строим кучу int length = array.length; // проходим от первого без ответвлений к корню for (int i = length / 2-1; i >= 0; i--) heapify(array, length, i); for (int i = length-1; i >= 0; i--) { int temp = array[0]; array[0] = array; array = temp; heapify(array, i, 0); } } Временная сложность Посмотрите на функцию heapify() – кажется, что все делается за O(1), верно? Но нет же: все портит рекурсивный вызов! Готовы посчитать, сколько вызовов нужно в наихудшем сценарии? В худшем случае рекурсивный вызов дойдет до самой вершины пирамиды прыжками к родителям каждого узла в отношении i/2. Всего потребуется log n прыжков до вершины, значит, сложность равна O(log n). Это ещё не всё! В связи с циклами for, которые итерируют весь массив, сложность heapSort() равна O(n). Это дает нам суммарную сложность пирамидальной сортировки O(nlog n). Быстрая сортировка На этом участнике нашего топа мы закончим разбирать примеры алгоритмов сортировки. Перед вами очередной алгоритм техники «разделяй и властвуй». Он выбирает один элемент массива в качестве стержня и сортирует остальные элементы вокруг (меньшие элементы налево, большие направо). Так соблюдается правильная позиция самого «стержня». Затем алгоритм рекурсивно повторяет сортировку для правой и левой частей. Реализация static int partition(int[] array, int begin, int end) { int pivot = end; int counter = begin; for (int i = begin; i < end; i++) { if (array < array[pivot]) { int temp = array[counter]; array[counter] = array; array = temp; counter++; } } int temp = array[pivot]; array[pivot] = array[counter]; array[counter] = temp; return counter; } public static void quickSort(int[] array, int begin, int end) { if (end <= begin) return; int pivot = partition(array, begin, end); quickSort(array, begin, pivot-1); quickSort(array, pivot+1, end); } Временная сложность Временную сложность алгоритма быстрой сортировки можно описать следующим уравнением: В наихудшем сценарии наибольший и наименьший элементы всегда выбираются в качестве стержня. Тогда уравнение приобретает вид: Получается O(n^2). На фоне алгоритмов сортировки со сложностью O(nlog n), выглядит не очень :( На практике быстрая сортировка применяется широко. Судите сами: у алгоритма очень хорошее среднее время запуска, также равное O(nlog n), он эффективен для больших потоков ввода. И на этом преимущества не заканчиваются! Алгоритм не занимает дополнительного пространства, вся сортировка происходит «на месте», отсутствуют затратные вызовы распределения, из-за чего его часто предпочитают сортировке слиянием. На этом всё!
  8. Как опубликовать свою Python библиотеку на PyPI Делаем так, чтобы вашу библиотеку на Python любой мог установить с помощью pip install. Трудно представить программу Python без набора операторов import. Но как опубликовать библиотеку, чтобы её также легко могли импортировать другие разработчики? Благодаря импортированию, модули Python удобно использовать В этом руководстве мы покажем на примере, как создать собственную библиотеку Python, которую можно будет установить с помощью менеджера пакетов pip, как другие сторонние решения. Если ваш код может приносить пользу, это отличный способ внести вклад в сообщество Python. Любой разработчик сможет установить пакет и импортировать его. 1. Создаём элементы библиотеки Публикация пакета требует знания некоторых деталей. В этом примере мы будем рассматривать предварительно опубликованный пакет под названием dist_alx. Чтобы вам было удобнее сверяться, эта директория доступна в виде zip-папкив репозитории на GitHub. Каталог содержит все необходимые элементы, которые должен иметь пакет перед публикацией. Мы будем использовать этот пример в качестве эталона, поскольку все пакеты Python должны иметь одинаковую структуру. Если вам интересно, что именно делает пакет dist_alx, это объяснено там же в README-файле. Корневой каталог dist_alx содержит следующие файлы и директории: setup.py – содержит метаданные пакета, setup.cfg – конфигурационный файл, используемый для хранения настроек, подпапку с тем же именем, что и родительская папка (в данном примере dist_alx), где хранится фактический код вашего пакета на Python. Подробнее об указанных файлах можно прочитать в документации библиотеки setuptools. Примеры содержания файлов в нашем случае: setup.py from setuptools import setup setup(name='dist_alx', version='0.0', description='Gaussian and Binomial distributions', packages=['dist_alx'], author_email='mihajlovic.aleksa@gmail.com', zip_safe=False) setup.cfg [egg_info] tag_build = tag_date = 0 Любая публикуемая на PyPI библиотека обязана иметь три вышеуказанных элемента. Помимо этого, пакет должен выполнять следующие условия: Уникальное имя библиотеки. Никакие два существующих пакета Python не могут одинаково называться. Файл setup.py должен содержать параметры name и packages. Они также должны иметь то же имя, что и пакет (см. пример выше). Параметр version: в случае если что-то в пакете изменилось, нужно изменить и значение version. Файла setup.py должен иметь параметр author_email с адресом электронной почты для связи. Используйте действующий e-mail, это важно для дальнейшей регистрации программы в репозитории PyPI. 2. Подготавливаем код Как указано выше, внутри вложенной папки должен находиться фактический код библиотеки. Если вы откроете подпапку dist_alx, вы увидите, что она содержит файл _init_.py. По умолчанию ядро Python ищет файл _init_.py в качестве отправной точки при чтении кода. Файл _init_.py связан со всеми другими сценариями Python в подпапке. Например, в нашем файле _init_.py, есть строка импорта from .Gaussiandistribution import Gaussian. Все .py-файлы связаны. Хорошей идеей, прежде чем пытаться написать код пакета, будет обновить свои знания о классах Python. 3. Создаём аккаунт PyPI Большинство общедоступных пакетов Python хранятся в репозитории PyPI. При установке пакета на свой компьютер инструкцией pip, вы фактически скачиваете его из репозитория PyPI. Соответственно для публикации нового пакета его нужно наоборот, загрузить на сервер PyPI. Чтобы опубликовать библиотеку, нужно завести аккаунт(это бесплатно). Адрес электронной почты должен быть тот же, что внутри setup.py в параметре author_email. Скриншот окна регистрации в репозитории PyPI 4. Публикуем библиотеку Публикация осуществляется из командной строки или терминала. Команды идентичны в Windows и Linux. Для публикации нам потребуется установить две библиотеки – setuptools и twine: pip install setuptools pip install twine Переходим к родительскому каталогу. Развёртываем пакет запустив setup.py: python setup.py sdist Обратите внимание, что теперь в родительской папке будут созданы две новых директории (egg-info и dist). Теперь с помощью twine развёртываем пакет на PyPI: twine upload dist/* Здесь нужно указать своё имя пользователя и пароль PyPI. Вуаля! Пакет размещён на сервере и готов к использованию! Переходим в учётную запись на PyPI, идём в раздел Your projects. Если всё в порядке, вы увидите библиотеку. Обратите внимание, что PyPI не поддерживает на сайте символ подчёркивания в именах файлов. Поэтому dist_alx отображается как dist-alx. Однако это не влияет на использование библиотеки – при последующем импорте мы будем вводить, как и задумано, dist_alx. 5. Используем Переходим к любимому клиенту Python (например, Jupyter) и устанавливаем пакет с помощью pip. Импортируем и пользуемся в своё удовольствие! Заключение Прежде чем размещать пакет на главном сервере, опубликуйте его сначала на тестовом сервере PyPI. Итак, последовательность создания библиотеки Python следующая: Изготовление необходимых элементов упаковки, проверка значений параметров. Подготовка кода пакета. Создание учётной записи PyPI (если её ещё не было). Публикация пакета на PyPI. Тестирование: pip-установка, импорт и запуск модуля.
  9. Oh1

    Лучшие IDE для Java

    Лучшие IDE для Java Лучшая бесплатная IDE: NetBeans NetBeans - мощнейшая среда разработки с открытым исходным кодом, ориентированная на интернет, мобильные и настольные приложения. Работает с Linux, Windows, MacOS и даже Oracle Solaris. Несмотря на то, что NetBeans позволяет работать на нескольких языках, в среде разработчиков она считается Java-ориентированной. Она прекрасно взаимодействует с JPA, JSP, Struts, Spring и библиотекой Hibernate. Лучшая коммерческая IDE: IntelliJ IDEA По правде говоря, IntelliJ IDEA распространяется в двух версиях, одна из которых совершенно бесплатная - Free Community Edition. Причём для начинающего разработчика данного пакета хватит с головой. В частности, IDE Android Studio, речь о которой пойдёт чуть позднее, основана именно на этой версии. В платной же версии вы получаете поддержку фреймворков Spring (Spring MVC framework, Spring Security, Spring Boot, Spring Integration и т. д.), Node.js, Angular React, Grails, возможность использовать дополнительные языки (javascript, typescript, coffeescript) и взаимодействовать почти со семи популярными серверами (Tomcat, TomEE, GlassFish, JBoss, WildFly, Weblogic, WebSphere, Geronimo, Virgo и т. д.). Самая популярная IDE: Eclipse Точную цифру привести практически невозможно, но практически любой Java-разработчик с опытом работы более 2 лет сталкивался с этой IDE. Победителем в этой номинации Eclipse удалось стать благодаря большому сообществу, тонне полезной информации и бесчисленному количеств плагинов. Как и с предыдущими экземплярами, Eclipse поддерживает несколько языков, но воспринимается как приверженец Java. Cамая универсальная IDE: JDeveloper Ещё один продукт от Oracle с массой преимуществ, среди которых поддержка системы контроля версий и облачного сервиса Oracle, он упакован SQL Developer, PL / SQL обработчиком запросов, WebLogic Server, редакторами HTML, CSS, JavaScript, JSF, JSP, WSDL и ещё огромным количеством всевозможных полезностей. Лучшая для Android: Android Studio Было бы странно, если победителем в этой номинации стала какая-нибудь другая IDE. Помимо всех возможностей, который вам дарит исходная IDE IntelliJ IDEA, Android Studio включает в себя немало надстроек от Google, как чисто визуальных (макеты, форматы, GPU профайлер), так и функциональных (JUnit 4 и Firebase Test Lab для тестирования и отладки, система сборки Gradle, Instant Run). Лучшая IDE для обучения: DrJava Именно к такому выводу пришла команда разработчиков под названием JavaPLT, представляющие университет Райса. Оно и неудивительно, учитывая, что DrJava - их детище. Впрочем, оставив шутки в стороне, стоит признать, что DrJava действительно прекрасно подойдёт новичкам, ведь данная IDE даже не ставит своей целью соперничество с выше названными. Главное её преимущество - предельно быстрая настройка и переход к непосредственному написанию кода. В качестве конкурентов можно на схожих условиях рассмотреть BlueJ, JGrasp и Greenfoot. Самая перспективная IDE: MyEclipse Приветственная надпись на странице скачивания гласит “The best Java EE IDE enhanced for the full stack developer”. Что ж, это весьма нескромно, совсем не подкреплено фактами, но по правде говоря - недалеко от истины. В сущности, MyEclipse - это Eclipse, где всё изначально “привинчено”, “допилено” и ещё немного расширено. К услугам разработчика предлагается несколько версий, две основные - стандартная и профессиональная. Стандартная - это как раз Eclipse в новой оболочке, а Professional содержит мобильный веб-симулятор, редактор картинок, UML-редактор, шаблоны, надстройки - в общем, всё, что сделает создание продукта значительно проще.
  10. Мониторинг файлов вместе с Java NIO Небольшой урок о том, как с помощью пакета java.nio.file написать класс для наблюдения за изменениями состояния файлов в директории. Java NIO или Java New I/O это крайне полезный пакет, позволяющий использовать асинхронный ввод/вывод. Сегодня с помощью java.nio.file, следуя паттерну Observer, мы реализуем свой класс для наблюдения за состоянием файлов в папке. Наш план: Первым делом создадим WatchService. Потом переменную Path, указывающую на папку, которую планируем мониторить. Далее бесконечный цикл наблюдения. Когда происходит интересующее нас событие, класс WathKeyпомещает его в очередь наблюдателя. После обработки события мы должны вернуть ключ в состояние готовности, вызвав метод reset(). Если метод вернёт false, то ключ больше не действителен, цикл можно завершить. WatchService watchService = FileSystems.getDefault().newWatchService(); Path path = Paths.get("c:\\directory"); //будем следить за созданием, изменение и удалением файлов. path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); boolean poll = true; while (poll) { WatchKey key = watchService.take(); for (WatchEvent<?> event : key.pollEvents()) { System.out.println("Event kind : " + event.kind() + " - File : " + event.context()); } poll = key.reset(); } Данный код должен вывести в консоль следующее: Event kind : ENTRY_CREATE - File : file.txt Event kind : ENTRY_DELETE - File : file.txt Event kind : ENTRY_CREATE - File : test.txt Event kind : ENTRY_MODIFY - File : test.txt Watch Service API довольно низкоуровневая штука, мы реализуем на его основе свой более высокоуровневый API. Начнём с написания класса FileEvent. Так как это объект состояния события, его необходимо унаследовать от EventObjectи передать в конструктор ссылку на файл, за котором будем наблюдать. FileEvent.java import java.io.File; import java.util.EventObject; public class FileEvent extends EventObject { public FileEvent(File file) { super(file); } public File getFile() { return (File) getSource(); } } Теперь создаём интерфейс слушателя FileListener, наследуемый отjava.util.EventListener. Этот интерфейс должны реализовать все слушатели, которые будут подписываться на события нашей папки. FileListener.java import java.util.EventListener; public interface FileListener extends EventListener { public void onCreated(FileEvent event); public void onModified(FileEvent event); public void onDeleted(FileEvent event); } Наконец, создаём класс, который будет хранить в себе список слушателей, подписанных на папку. Назовём его FileWatcher. public class FileWatcher { protected List<FileListener> listeners = new ArrayList<>(); protected final File folder; public FileWatcher(File folder) { this.folder = folder; } public List<FileListener> getListeners() { return listeners; } public FileWatcher setListeners(List<FileListener> listeners) this.listeners = listeners; return this; } } Хорошей идеей будет реализовать класс FileWatcher как Runnable, чтобы иметь возможность запускать наблюдение в виде демона, если указанная папка существует. FileWatcher.java public class FileWatcher implements Runnable { public void watch() { if (folder.exists()) { Thread thread = new Thread(this); thread.setDaemon(true); thread.start(); } } @Override public void run() { // пока оставим без реализации } } Так как в методе run() будут создаваться объекты WatchService, использующие внешние ресурсы (ссылки на файлы), все события будем хранить в статическом списке. Такая реализация позволит вызвать метод close() из любого потока, ждущего ключ. А также вызвать исключение ClosedWatchServiceException, чтобы отменить наблюдение без утечки памяти. @Override public void contextDestroyed(ServletContextEvent event) { for (WatchService watchService : FileWatcher.getWatchServices()){ try { watchService.close(); } catch (IOException e) {} } } public class FileWatcher implements Runnable { protected static final List<WatchService> watchServices = new ArrayList<>(); @Override public void run() { try (WatchService watchService = FileSystems.getDefault().newWatchService()) { Path path = Paths.get(folder.getAbsolutePath()); path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); watchServices.add(watchService); boolean poll = true; while (poll) { poll = pollEvents(watchService); } } catch (IOException | InterruptedException | ClosedWatchServiceException e) { Thread.currentThread().interrupt(); } } protected boolean pollEvents(WatchService watchService) throws InterruptedException { WatchKey key = watchService.take(); Path path = (Path) key.watchable(); for (WatchEvent<?> event : key.pollEvents()) { notifyListeners(event.kind(), path.resolve((Path) event.context()).toFile()); } return key.reset(); } public static List<WatchService> getWatchServices() { return Collections.unmodifiableList(watchServices); } } Когда происходит интересующее нас событие и путь корректен, мы уведомляем слушателей о событии. Если была создана новая директория, то для неё будет инициализирован новый экземпляр FileWatcher. FileWatcher.java public class FileWatcher implements Runnable { protected void notifyListeners(WatchEvent.Kind<?> kind, File file) { FileEvent event = new FileEvent(file); if (kind == ENTRY_CREATE) { for (FileListener listener : listeners) { listener.onCreated(event); } if (file.isDirectory()) { // создаем новый FileWatcher для отслеживания новой директории new FileWatcher(file).setListeners(listeners).watch(); } } else if (kind == ENTRY_MODIFY) { for (FileListener listener : listeners) { listener.onModified(event); } } else if (kind == ENTRY_DELETE) { for (FileListener listener : listeners) { listener.onDeleted(event); } } } } Полная реализация класса FileWatcherбудет выглядеть так: FileWatcher.java import static java.nio.file.StandardWatchEventKinds.*; import java.io.File; import java.io.IOException; import java.nio.file.ClosedWatchServiceException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.WatchEvent; import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class FileWatcher implements Runnable { protected List<FileListener> listeners = new ArrayList<>(); protected final File folder; protected static final List<WatchService> watchServices = new ArrayList<>(); public FileWatcher(File folder) { this.folder = folder; } public void watch() { if (folder.exists()) { Thread thread = new Thread(this); thread.setDaemon(true); thread.start(); } } @Override public void run() { try (WatchService watchService = FileSystems.getDefault().newWatchService()) { Path path = Paths.get(folder.getAbsolutePath()); path.register(watchService, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); watchServices.add(watchService); boolean poll = true; while (poll) { poll = pollEvents(watchService); } } catch (IOException | InterruptedException | ClosedWatchServiceException e) { Thread.currentThread().interrupt(); } } protected boolean pollEvents(WatchService watchService) throws InterruptedException { WatchKey key = watchService.take(); Path path = (Path) key.watchable(); for (WatchEvent<?> event : key.pollEvents()) { notifyListeners(event.kind(), path.resolve((Path) event.context()).toFile()); } return key.reset(); } protected void notifyListeners(WatchEvent.Kind<?> kind, File file) { FileEvent event = new FileEvent(file); if (kind == ENTRY_CREATE) { for (FileListener listener : listeners) { listener.onCreated(event); } if (file.isDirectory()) { new FileWatcher(file).setListeners(listeners).watch(); } } else if (kind == ENTRY_MODIFY) { for (FileListener listener : listeners) { listener.onModified(event); } } else if (kind == ENTRY_DELETE) { for (FileListener listener : listeners) { listener.onDeleted(event); } } } public FileWatcher addListener(FileListener listener) { listeners.add(listener); return this; } public FileWatcher removeListener(FileListener listener) { listeners.remove(listener); return this; } public List<FileListener> getListeners() { return listeners; } public FileWatcher setListeners(List<FileListener> listeners) { this.listeners = listeners; return this; } public static List<WatchService> getWatchServices() { return Collections.unmodifiableList(watchServices); } } Последний штрих – создание класса FileAdapter, простейшей реализации интерфейса FileListener. FileAdapter.java public abstract class FileAdapter implements FileListener { @Override public void onCreated(FileEvent event) { //реализация не предусмотрена } @Override public void onModified(FileEvent event) { //реализация не предусмотрена } @Override public void onDeleted(FileEvent event) { //реализация не предусмотрена } } Чтобы убедиться в работоспособности написанного кода, можно использовать несложный класс FileWatcherTest. FileWatcherTest.java import static org.junit.Assert.*; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.junit.Test; public class FileWatcherTest { @Test public void test() throws IOException, InterruptedException { File folder = new File("src/test/resources"); final Map<String, String> map = new HashMap<>(); FileWatcher watcher = new FileWatcher(folder); watcher.addListener(new FileAdapter() { public void onCreated(FileEvent event) { map.put("file.created", event.getFile().getName()); } public void onModified(FileEvent event) { map.put("file.modified", event.getFile().getName()); } public void onDeleted(FileEvent event) { map.put("file.deleted", event.getFile().getName()); } }).watch(); assertEquals(1, watcher.getListeners().size()); wait(2000); File file = new File(folder + "/test.txt"); try(FileWriter writer = new FileWriter(file)) { writer.write("Some String"); } wait(2000); file.delete(); wait(2000); assertEquals(file.getName(), map.get("file.created")); assertEquals(file.getName(), map.get("file.modified")); assertEquals(file.getName(), map.get("file.deleted")); } public void wait(int time) throws InterruptedException { Thread.sleep(time); } } Несмотря на кажущуюся простоту, наблюдатели – мощный инструмент автоматизации процессов. С помощью них, например, можно автоматически перезапускать скрипты на продакшене, если один из них был изменён. Уменьшение человеческого фактора – благо для программиста.
  11. Почувствуй силу: cовременные инструменты С++ Подробно о том, как модернизировать старый проект с помощью встроенных инструментов VS 2019 и Clang Power Tools. В этой статье рассмотрим современные инструменты, посредством которых можно улучшить старые проекты на С++. Благодаря новейшим компиляторам и оптимизаторам можно перелопатить кучу кода, не тратя на это полжизни. Введение Приходилось ли вам встречать ужасные строчки вроде этих? float* pfloats = new float[10]; // no delete [] later! :) int x = pfloats[0]; Заметить ошибку нетрудно. Даже базовый компилятор услужливо предупредит о возможной потере данных при конвертации float в int. Но что делать с более сложным кодом в работающем проекте? Visual Studio 2019 имеет встроенный анализатор кода, который будет давать довольно полезные подсказки. Рассмотрим следующий пример: #include <iostream> class SuspiciousType { public: SuspiciousType() { } ~SuspiciousType() { std::cout << "destructor!\n"; } int compute(int z) { return x + y + z; } int x; int y; }; int main() { SuspiciousType st; float* pfloats = new float[10]{ 100.5f }; int z = pfloats[0]; } В VS 2019 мы можем настроить правила проекта под свои нужды. Включить все пункты или создать детальный профиль. При включении анализатора сразу получаем ряд предупреждений. Сначала для класса SuspiciousType: cpptests.cpp(5): warning C26495: Variable 'SuspiciousType::x' is uninitialized. Always initialize a member variable (type.6). cpptests.cpp(5): warning C26455: Default constructor may not throw. Declare it 'noexcept' (f.6). cpptests.cpp(6): warning C26432: If you define or delete any default operation in the type 'class SuspiciousType', define or delete them all (c.21). cpptests.cpp(6): warning C26447: The function is declared 'noexcept' but calls function 'operator<<<std::char_traits<char> >()' which may throw exceptions (f.6). cpptests.cpp(8): warning C26440: Function 'SuspiciousType::compute' can be declared 'noexcept' (f.6). А потом и в функции main: cpptests.cpp(16): warning C26462: The value pointed to by 'pfloats' is assigned only once, mark it as a pointer to const (con.4). cpptests.cpp(17): warning C26496: The variable 'z' is assigned only once, mark it as const (con.4). cpptests.cpp(17): warning C26481: Don't use pointer arithmetic. Use span instead (bounds.1). cpptests.cpp(16): warning C26409: Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11). cpptests.cpp(16): warning C26400: Do not assign the result of an allocation or a function call with an owner<T> return value to a raw pointer, use owner<T> instead (i.11). Как видите, среда разработки успешно обнаружила все существенные недочёты. Более того, если предупреждение связано с несоблюдением гайдлайна – в конце будет указан конкретный пункт, который легко поправить, найдя его в официальном руководстве. Как бонус, Visual Studio теперь подчёркивает зелёной волнистой линией элементы когда, которые считает устаревшими или сомнительными. При клике на код показываются подобные комментарии. Если вы не используете последнюю версию Visual Studio, то обратите внимание на Clang Power Tools. Это расширение для студии даёт приблизительно такой же набор функций. Но разбирать откровенно дурной код это одно, а можно ли извлечь пользу из этих инструментов на реальном проекте? Рассмотрим проект покрупнее В декабре 2019-го я открыл свой старый проект со времён учёбы. Это визуализация алгоритмов сортировки, написанная в далёких 2005/2006 годах на старом С++, Win32Api и OpenGL. Код можно посмотреть в репозитории. Программа принимает на вход массив значений и обрабатывает их со скоростью около 30 действий в секунду. Каждый элемент отрисовывается на диаграмме. Зелёным обозначается элемент, к которому производится обращение в данный момент, а голубым – сортируемая в данный момент часть массива. Демонстрацию на примере Quick sort вы видите в гифке выше. Несмотря на красивый внешний вид, внутри есть ряд ужасных идей, так что не пугайтесь, если решите изучить репозиторий. Это довольно интересный опыт – изучать свой код, написанный 15 лет назад. Я решил переделать проект под VS 2019. Чтобы вспомнить, как всё работало, я реализовал Quick sort, которого изначально в программе не было. Приложение использует С++ 03 и достаточно велико. Так что это отличный пример, для демонстрации вариантов модернизации старых проектов. Сообщения о проблемах Когда я включил анализатор кода на полную, то получил 956 предупреждений... Этого стоило ожидать, давайте рассмотрим их подробнее. Использование констант Компилятор видит переменные, не меняющие значение в процессе выполнения, и автоматически предлагает использовать константы. Например, для такого кода: case cmYawPitchRoll: { float r = cos(m_fPitch); float x = r*sin(m_fYaw); float y = sin(m_fPitch); float z = -r*cos(m_fYaw); m_vTarget = VECTOR3D(x, y, z); m_vUp = VECTOR3D(sin(m_fRoll), cos(m_fRoll), 0.0f); break; } Предупреждение выглядит так: Warning C26496 The variable 'r' is assigned only once, mark it as const (con.4). Это касается и функций: // ang * M_PI / 180.0f inline float DegToRad(float a) { return a*0.01745329252f; }; // rads * 180.0f / M_PI inline float RadToDeg(float a) { return a*57.29577951f; }; Предупреждение: Warning C26497 The function 'DegToRad' could be marked constexpr if compile-time evaluation is desired (f.4). Неинициализированные переменные К сожалению, тогда в моём коде это было распространённой ошибкой. Например, для CGLFont я забыл про m_fSize. CGLFont(): m_FontMode(fmNone), m_iList(0), m_iTexture(0) { } И получил такое предупреждение: Warning C26495 Variable 'CGLFont::m_fSize' is uninitialized. Always initialise a member variable (type.6). Избыточное использование указателей В 2005 я не особо много знал об умных указателях и везде использовал new и delete. В современном C++ мы должны избегать такого подхода. VisualStudio заботливо найдёт места, нуждающиеся в модернизации: g_Algorithms[ABUBBLE_SORT] = new CBubbleSortAlgorithm(); g_Algorithms[ASHAKER_SORT] = new CShakerSortAlgorithm(); И предупреждение: Warning C26409 Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11). К тому же компилятор может находить проблемы с null pointer. Например, в таком коде, я получил уведомление о непроверенном на null указателе: Render(CAVSystem *avSystem) { ColorType ct; avSystem->BeginDrawing(1.0, (int)m_vArray.size()); ... Warning C26429 Symbol 'avSystem' is never tested for nullness, it can be marked as not_null (f.23). А дальше мне надо решить, добавить проверку или пометить указатель, как not_null. Переход на nullptr В этом нет ничего сложного, заменить все NULL из моего кода на nullptr из C++ 11 – тривиальная задача. Clang-tidy даже может сделать это автоматически. Использование noexept В C++ 11 мы получили спецификатор noexept для оптимизации генерируемых бинарных файлов. Естественно, в своём старом коде я этого использовать не мог и получил кучу предупреждений. Например, для такого кода: void SetTempoBPS(double fTempo) { m_fTempo = fTempo; } void SetTempoBPM(double fTempo) { m_fTempo = fTempo/60.0; } double GetTempoBPS() { return m_fTempo; } double GetTempoBPM() { return m_fTempo*60.0; } VisualStudio дала следующее предупреждение: Warning C26440 Function 'CBeat::SetTempoBPS' can be declared 'noexcept' (f.6). И да, геттеры должны быть константными... Больше noexept Иногда добавлением спецификатора проблему не решить. Приходится рассматривать вариант полного обновления функции. Вот что я получил: Warning C26447 The function is declared 'noexcept' but calls function 'Destroy()' which may throw exceptions (f.6). Для кода: CGLApp::~CGLApp() { Destroy(); } Использование override С override похожая история. В 2005 у нас не было такого модификатора, поэтому для интерфейса, определяющего три чисто виртуальные функции: // в интерфейсе virtual void Init(CViData *viData) = 0; virtual void Step() = 0; virtual void Stop() = 0; у меня не было возможность выразить это в производном классе и приходилось писать так: // в производном: void Init(CViData *viData); void Step(); void Stop(); C++ 11 дала нам возможность это изменить: // в производном: void Init(CViData *viData) override; void Step() override; void Stop() override; Правило нуля По некой мистической причине я сделал кучу пустых деструкторов, и компилятор это заметил: Warning C26432 If you define or delete any default operation in the type 'class CCamera', define or delete them all (c.21). Заключение Возвращаться к своим древним проектам довольно весело, особенно если вам нравятся их идеи. Удивительно, как с годами меняются инструменты и походы к решениям. Современные компиляторы и средства анализа могут быть отличными напарниками и делать неплохое базовое ревью кода. Иногда даже указывать на места, где стоит подтянуть теорию современной разработки. Конечно, вы можете обновить весь свой код, полагаясь только на свою «силу», знания и опыт, но ведь можно использовать современные инструменты и сохранить кучу времени? Visual Studio только один из многих.
  12. 1) Чтобы сделать хороший сайт - надо хорошо знать такие темы как HTML+CSS и PHP+MySQL, но даже без них Вы можете сделать простенький сайт выполним инструкцию далее 2) Веб-сервер, способный обрабатывать запросы. Для профессиональной работы необходимо заказывать у хостинг-провайдеров, но делая для себя можно установить OpenServer Basic на Windows. Установка достаточно простая, после установки его включить и активировать (появится зеленый флаг), и далее поместить в папку domains/localhost файл index.php следующего содержания (можно даже блокнотом отредактировать): <?php echo 'Hello World'; ?> И ввести в адресной строке: http://localhost/ . Если у Вас вывелась надпись Hello World, то Вы всё сделали правильно и у Вас появилась надпись на экране Hello World, то мы можем приступать к созданию простенького сайта далее. Если же у Вас возникли трудности с установкой, то советую посмотреть видео-урок посвященный установке: 3) Третьим шаг выбор IDE - программы для редактирования кода. Обычный блокнот в Windows может помочь, но всё же лучше воспользоваться профессиональным редактором PHPStorm. 4) И вот теперь Вы готовы писать код. Предлагаю создать 3 файла: index.php , main.php , contacts.php следующего содержания. index.php: <?php error_reporting(-1); ini_set('display_errors',1); header('Content-Type: text/html; charset=utf-8'); $page = (isset($_GET['page']) ? $_GET['page'] : 'main'); ?> <html> <head> <title>Наш первый сайт</title> </head> <body> <header> <nav> <a href="index.php?page=main">Главная страница сайта</a> | <a href="index.php?page=contacts">Контакты</a> </nav> </header> <?php include basename($page).'.php'; ?> <footer> Сайт сделан сегодня и все права принадлежат его создателю :) </footer> </body> </html> main.php: <div> Главная страница сайта. Приветствую посетителей </div> contacts.php: <div> Мой адрес не дом и не улица Мой адрес сегодня такой www.oh1.com </div> 5) Наслаждаемся готовым сайтом.
  13. 1. echo быстрее чем print. Да и писать на одну букву меньше. Но скорее этот совет для общего развития. Большинство программистов используют echo. Прирост производительности минимален. И в серьезных проектах echo редко встречающаяся конструкция. Потому что вывод как правило сначала собирается, а потом выбрасывается. 2. Строки заключенные в одинарные кавычки ', а не в вдвойные " быстрее, потому что PHP ищет переменные внутри строк, заключенных в двойные кавычки. Заключайте строки в одинарные кавычки, когда в ней нет переменных. [статья] Визуально строки в одинарных кавычках читаются и воспринимаются лучше. Но когда это просто строки, а не переменные строки, соединенные с помощью конкатенации. В таком случае прирост производительности дают как раз переменные вставленные в строки, обрамленные двойными кавычками. К тому же увлечение конкатенацией выглядит нечитабельно. 3. Используйте sprintf вместо переменных, заключенных в двойные кавычки. Это примерно в 10 раз быстрее. [тест] Скорей всего это по причине того, что если использовать sprintf, то PHP обращается к С-функции sprintf. Что само собой быстрее чем если разбор строки будет производить сам PHP, написанный на С. Однако я не думаю что такой код будет достаточно легко воспринимаем. Возможно такое имеет смысл на очень высоконагруженных проектах, где ведется очень много работы со строками, и использование sprintf может дать ощутимый выйгрыш в производительности. Но я не представляю себе таких проектов. Обычно все валится в других местах. 4. Передавайте в echo несколько параметров, вместо того, чтобы использовать конкатенацию строк. [статья] Логично что это работает быстрее, за счет того что нет конкатенации, которая требует времени. А параметры один за другим выбрасываются в выходной поток. Насчет читабельности, мне лично множественные параметры читать удобней, чем конкатенацию, но возможно не всем так: echo $a, 'text', $b, 'more text'; // множественные параметры echo $a . 'text' . $b . 'text'; //конкатенация По примечанию eyes, по результатам теста phpbench.com, конкатенация константных строк для echo работает быстрее, чем если передавать их множественными параметрами 5. Не используйте вычисления числа элементов массива, длины строки и т.п., для определения последней итерации цикла в самом цикле. Установите максимальное значение для цикла for перед ним. Например: for ($x=0; $x < count($array); $x), вызывает count() при каждой итерации, используйте $max=count($array); перед циклом. ссылка [тест] Это давно известный факт. Тест показывает разницу в производительности в 10 раз. Если имеет место довольно большой массив данных, или подобные вычисления происходят часто, стоит задуматься. На читабельность не влияет никак.
  14. Oh1

    5 ценных Python-советов

    5 ценных Python-советов Нам нравится Python за универсальность и скорость разработки. Мы хотим, чтобы пайтонистов становилось больше, а их скиллы преумножались и прокачивались. 30 мая запускаем новый поток обучения разработчиков на Python. А пока делимся удобными приёмами, которые эксперт TechBeamers собрал из разных источников, включая вебинары по программированию на Python, Stack Overflow и Wikipedia. Прежде чем попасть в обзор, каждый из подходов прошёл экспертный отбор, то есть его удобство и эффективность проверены на практике. Эти советы пригодятся и разработчикам, и тестировщикам. Некоторые из них будут новыми и полезными даже для опытных программистов на Python. Среди причин создания такого пособия — растущая популярность Python как языка программирования, обеспечивающего высокую скорость разработки. Оглянитесь вокруг, и вы увидите, что он эффективен везде — от различных конфигурационных инструментов до анализа XML. Сейчас доступны разные версии Python, но большинство программистов предпочитают использовать Python версий 2.x и 3.x. Все предлагаемые советы работают в каждой из них. 1. Запуск скриптов Python На большинстве систем UNIX можно запускать скрипты Python из командной строки следующим образом: # run python script $ python MyFirstPythonScript.py 2. Запуск программ на Python из интерпретатора Интерактивный интерпретатор Python очень прост в использовании. Вы можете совершить свои первые шаги в освоении этого языка, просто набирая любые команды в консоли Python одну за другой, и немедленно получать результаты. Консоль Python можно запустить с помощью команды: # start python console $ python >>> <type commands here> *В этой статье весь код, идущий после >>>, нужно набирать в строке ввода Python. Важно помнить, что Python очень серьезно воспринимает табуляцию, так что если вы получаете какие-либо ошибки с её упоминанием, то исправьте выравнивание. 3. Использование функции enumerate() Функция enumerate() добавляет счетчик в итерируемый объект, в котором используется метод __iter__ , возвращающий итератор. Он может принимать последовательные значения индекса, начиная с нуля. И выдаёт ошибку IndexError, когда индексы больше недействительны. Типичный пример использования функции enumerate() — создание цикла по списку с отслеживанием индекса. Для этого можно использовать переменную в качестве счетчика. Но функция enumerate() позволяет сделать то же самое намного удобнее. # First prepare a list of strings subjects = ('Python', 'Coding', 'Tips') for i, subject in enumerate(subjects):   print(i, subject) # Output:   0 Python   1 Coding   2 Tips 4. Тип данных SET Тип данных «set» — это своего рода коллекция. Она стала частью Python, начиная с версии 2.4. Множество содержит неупорядоченную коллекцию уникальных и неизменяемых объектов. Это один из типов данных Python, реализующих множества из мира математики. Множества, в отличие от списков или кортежей, не могут содержать дублей. Если вы хотите создать множество, просто используйте встроенную функцию set() с последовательностью или другими итерируемыми объектами в качестве аргументов. # *** Create a set with strings and perform search in set objects = {"python", "coding", "tips", "for", "beginners"} # Print set. print(objects) print(len(objects)) # Use of "in" keyword. if "tips" in objects:   print("These are the best Python coding tips.") # Use of "not in" keyword. if "Java tips" not in objects:   print("These are the best Python coding tips not Java tips.") # ** Output   {'python', 'coding', 'tips', 'for', 'beginners'}   5   These are the best Python coding tips.   These are the best Python coding tips not Java tips. Добавление объектов в множество: # *** Lets initialize an empty set items = set() # Add three strings. items.add("Python") items.add("coding") items.add("tips") print(items) # ** Output   {'Python', 'coding', 'tips'} 5. Динамический ввод В Java, C++ и других статически типизированных языках нужно указывать тип данных возвращаемого значения функции и тип каждого её аргумента. Напротив, Python, как динамически типизированный язык, не вынуждает явно указывать типы данных. На основе присвоенных значений Python отслеживает их сам. Вот ещё одно хорошее определение динамической типизации: «Имена связываются с объектами во время выполнения с помощью операторов присваивания. И существует возможность прикрепить имя к объекту другого типа прямо во время выполнения программы». В следующем примере показано, как функция может проверять свои аргументы и как делать разные вещи в зависимости от типа аргументов. # Test for dynamic typing. from types import * def CheckIt (x):   if type(x) == IntType:     print("You have entered an integer.")   else:     print("Unable to recognize the input data type.") # Perform dynamic typing test CheckIt(999)   # Output:   # You have entered an integer. CheckIt("999")   # Output:   # Unable to recognize the input data type.