Alex
Підписатись

Alex

Приєднався: 4 роки тому | 9 Читає   16 Читачів | 2.1K

Адмін сайту

  1. Оператор == порівнює посилання.

    Метод equals порівнює значення.

    Отже, якщо ви хочете порівняти рядки на рівність, слід використовувати equals.

    Однак в деяких випадках рядки гарантовано представлені одним і тим же об'єктом завдяки пулу рядків (string interning). Ці випадки явно описані в специфікації мови Java .

    Оператор == використовується для перевірки, що два рядки вказують на один і той же об'єкт.

    // Ці рядки мають одне і те саме значення
    new String("test").equals("test") // --> true 
    
    // ...але це різні об'єкти
    new String("test") == "test" // --> false 
    
    // ...ці рядки також різні об'єкти
    new String("test") == new String("test") // --> false 
    
    // ...але ці рядки вказують а один і той самий об'єкт
    // дому що компілятор додає всі літерали в пул
    "test" == "test" // --> true 
    
    // Об'єднання літералів токож відбувається на стадії компіляції
    // тому вони вказують на один об'єкт
    "test" == "te" + "st" // --> true
    
    // але виклик substring() відбувається під час виконання,
    // в результаті виходять різні об'єкти
    "test" == "!test".substring(1) // --> false
    
    // Рядки з пулу можуть буди отримані за допомогою виклику intern().
    "test" == "!test".substring(1).intern() // --> true
    

    Треба відзначити, що == помітно швидше, ніж equals (порівняння посилання замість виклику методу і посимвольного порівняння, якщо рядки різної довжини), тому, якщо ви працюєте з рядками з пулу (або системного, або свого), заміна equals на == може привести до помітного прискорення. Але це трапляється дуже рідко.

    Остерігайтеся виклику equals на null! Оператор == прекрасно порівнює рядки, якщо один або більше з них дорівнює null, але виклик методу equals на рядок, що дорівнює null, призведе до виключення.

    Для порівняння рядків, які можуть бути рівні null, ви можете скористатися наступним методом:

    public static boolean equals(String str1, String str2) {
        return str1 == null ? str2 == null : str1.equals(str2);
    }
    

    Він присутній в деяких сторонніх бібліотеках, наприклад, в Apache Commons.

    Якщо ви користуєтеся сучасними середовищами розробки, то вони попередять, якщо ви спробуєте порівняти рядки за допомогою оператора ==. Завжди звертайте увагу на подібні попередження.


  2. Відмінності насправді кардинальні.

    Річ у тому, що в першому випадку у вас ліниве, а в другому - енергійне обчислення відповіді. Це означає, що елементи вихідної послідовності в енергійному випадку обчислюються всі і відразу, а в ледачому випадку - тільки коли запитані і тільки ті, що запитані.

    Подивімося, де з практичного боку є різниця.

    Для випадку ледачого обчислення вся послідовність не присутня повністю в пам'яті. Це означає, що при обробці по елементах у нас не виділяється пам'ять, і зберігається cache locality:

    IEnumerable GenerateHugeSequenceLazy()
    {
        for (int i = 0; i < 1000000; i++)
            yield return 13 * i;
    }
    
    IEnumerable GenerateHugeSequenceEager()
    {
        var result = new List();
        for (int i = 0; i < 1000000; i++)
            result.Add(13 * i);
        return result;
    }
    

    Обчислюємо функцію на всій послідовності, порівнюємо витрата пам'яті:

    var seqLazy = GenerateHugeSequenceLazy();
    // вичисляємо максимум вручну
    var max = 0;
    foreach (var v in seqLazy)
        if (v > max)
            max = v;
    
    var memLazy = GC.GetTotalMemory(forceFullCollection: false);
    
    var seqEager = GenerateHugeSequenceEager();
    // вичисляємо максимум вручну
    max = 0;
    foreach (var v in seqEager)
        if (v > max)
            max = v;
    
    var memEager = GC.GetTotalMemory(forceFullCollection: false);
    
    Console.WriteLine($"Memory footprint lazy: , eager: ");
    

    Результат:

    Memory footprint lazy: 29868, eager: 6323088
    

    Далі, у нас досить великі відмінності в розумінні операцій. Енергійні обчислення проводяться в момент виклику функції, в той час, як ледачі обчислення відбуваються в момент, коли ви користуєтеся результатом. А значить, для реального обчислення ледачої послідовності стан аргументів буде взято на момент перерахування. Ось приклад:

    IEnumerable DoubleEager(IEnumerable seq)
    {
        var result = new List();
        foreach (var e in seq)
            result.Add(e * 2);
        return result;
    }
    
    IEnumerable DoubleLazy(IEnumerable seq)
    {
        foreach (var e in seq)
            yield return e * 2;
    }
    

    Дивимося на відмінності:

    var seq = new List() { 1 };
    var eagerlyDoubled = DoubleEager(seq);
    var lazilyDoubled = DoubleLazy(seq);
    
    Console.WriteLine("Eager: " + string.Join(" ", eagerlyDoubled));
    Console.WriteLine("Lazy : " + string.Join(" ", lazilyDoubled));
    // виведе обидва рази 2, поки відмінностей немає
    
    seq.Add(2); // змінюємо вихідну послідовність
    
    Console.WriteLine("Eager: " + string.Join(" ", eagerlyDoubled)); // 2
    Console.WriteLine("Lazy : " + string.Join(" ", lazilyDoubled));  // 2 4
    

    Оскільки ліниве обчислення відбувається при перерахуванні, ми бачимо, що при зміні послідовності лінива версія підхоплює зміни.

    Інший приклад. Подивимося, що буде, якщо ми не обчислюємо всю послідовність. Обчислимо одну і ту ж послідовність енергійно та ліниво:

    IEnumerable Eager10()
    {
        Console.WriteLine("Eager");
        int counter = 0;
        try
        {
            var result = new List();
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"Adding: ");
                counter++;
                result.Add(i);
            }
            return result;
        }
        finally
        {
            Console.WriteLine($"Eagerly computed: ");
        }
    }
    
    IEnumerable Lazy10()
    {
        Console.WriteLine("Lazy");
        int counter = 0;
        try
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine($"Adding: ");
                counter++;
                yield return i;
            }
        }
        finally
        {
            Console.WriteLine($"Lazily computed: ");
        }
    }
    

    Беремо тільки 2 елементи з результату:

    foreach (var e in Eager10().Take(2))
        Console.WriteLine($"Obtained: ");
    
    foreach (var e in Lazy10().Take(2))
        Console.WriteLine($"Obtained: ");
    
    foreach (var e in Lazy10())
    {
        Console.WriteLine($"Obtained: ");
        if (e == 1)
            break;
    }
    

    Отримуємо такий висновок на консоль:

    Eager
    Adding: 0
    Adding: 1
    Adding: 2
    Adding: 3
    Adding: 4
    Adding: 5
    Adding: 6
    Adding: 7
    Adding: 8
    Adding: 9
    Eagerly computed: 10
    Obtained: 0
    Obtained: 1
    Lazy
    Adding: 0
    Obtained: 0
    Adding: 1
    Obtained: 1
    Lazily computed: 2
    Lazy
    Adding: 0
    Obtained: 0
    Adding: 1
    Obtained: 1
    Lazily computed: 2
    

    Бачите різницю? Лінивий варіант прогнав цикл всього два рази, і не обчислював «хвіст» послідовності.

    Ще одна різниця між випадками - коли повідомляються помилки. У разі енергійного обчислення вони повідомляються відразу. У разі ледачого - лише при перерахуванні результату. приклад:

    IEnumerable CheckEagerly(int value)
    {
        if (value == 0)
            throw new ArgumentException("value cannot be 0");
        return new List { value };
    }
    
    IEnumerable CheckLazily(int value)
    {
        if (value == 0)
            throw new ArgumentException("value cannot be 0");
        yield return value;
    }
    

    Застосовуємо try / catch:

    Console.WriteLine("Eager:");
    IEnumerable seqEager = null;
    try
    {
        seqEager = CheckEagerly(0);
    }
    catch (ArgumentException)
    {
        Console.WriteLine("Exception caught");
    }
    
    if (seqEager != null)
        foreach (var e in seqEager)
            Console.WriteLine(e);
    
    Console.WriteLine("Lazy:");
    IEnumerable seqLazy = null;
    try
    {
        seqLazy = CheckLazily(0);
    }
    catch (ArgumentException)
    {
        Console.WriteLine("Exception caught");
    }
    
    if (seqLazy != null)
        foreach (var e in seqLazy)
            Console.WriteLine(e);
    

    Отримуємо результат:

    Eager:
    Exception caught
    Lazy:
    
    Unhandled Exception: System.ArgumentException: value cannot be 0
       at Program.d__3.MoveNext() in ...\Program.cs:line 59
       at Program.Run() in ...\Program.cs:line 45
       at Program.Main(String[] args) in ...\Program.cs:line 13
    

    Для того, щоб отримати «найкраще з обох світів», тобто, ліниве обчислення, але енергійну перевірку аргументів, найпростіше розділити функцію на дві: енергійну перевірку і ліниве обчислення без перевірки. Для сучасних версій C# зручно використовувати вкладені функції:

    IEnumerable CheckEagerlyEnumerateLazily(int value)
    {
        if (value == 0)
            throw new ArgumentException("value cannot be 0");
        return Impl();
    
        IEnumerable Impl()
        {
            yield return value;
        }
    }
    

    Перевіряємо:

    Console.WriteLine("Recommended way:");
    IEnumerable seqLazy = null;
    try
    {
        seqLazy = CheckEagerlyEnumerateLazily(0);
    }
    catch (ArgumentException)
    {
        Console.WriteLine("Exception caught");
    }
    
    if (seqLazy != null)
        foreach (var e in seqLazy)
            Console.WriteLine(e);
    

    і отримуємо

    Recommended way:
    Exception caught
    

    Ще один випадок відмінності - залежність від зовнішніх даних в процесі обчислення. Наступний код намагається впливати на обчислення, змінюючи глобальний стан. (Це не дуже хороший код, не робіть так в реальних програмах!)

    bool evilMutableAllowCompute;
    
    IEnumerable EagerGet5WithExternalDependency()
    {
        List result = new List();
        for (int i = 0; i < 5; i++)
        {
            if (evilMutableAllowCompute)
                result.Add(i);
        }
        return result;
    }
    
    IEnumerable LazyGet5WithExternalDependency()
    {
        for (int i = 0; i < 5; i++)
        {
            if (evilMutableAllowCompute)
                yield return i;
        }
    }
    

    Використовуємо:

    Console.WriteLine("Eager:");
    evilMutableAllowCompute = true;
    foreach (var e in EagerGet5WithExternalDependency())
    {
        Console.WriteLine($"Obtained: ");
        if (e > 0)
            evilMutableAllowCompute = false;
    }
    
    Console.WriteLine("Lazy:");
    evilMutableAllowCompute = true;
    foreach (var e in LazyGet5WithExternalDependency())
    {
        Console.WriteLine($"Obtained: ");
        if (e > 0)
            evilMutableAllowCompute = false;
    }
    

    Результат:

    Eager:
    Obtained: 0
    Obtained: 1
    Obtained: 2
    Obtained: 3
    Obtained: 4
    Lazy:
    Obtained: 0
    Obtained: 1
    

    Ми бачимо, що зміна глобальних даних навіть після формального відпрацювання ледачої функції може впливати на обчислення.

    (Це ще один аргумент на користь того, що функціональне програмування і мутабельний стан погано поєднуються.)


  3. Оператор представлений знаком питання ?. Його також називають «тернарний», так як цей оператор, єдиний в своєму роді, має три аргументи.

    Синтаксис:

    let result = умова ? значення1 : значення2;
    

    Спочатку обчислюється умова: якщо вона істинна, тоді повертається значення1, в іншому випадку - значення2.

    У Вашому випадку:

    nr=(previous_number=='') ? 1 : parseInt(previous_number); 
    

    Що означає

    let nr;
    if(previous_number == ''){
      nr = 1;
    } else { 
      nr = parseInt(previous_number);
    }

  4. Команда:

    ALTER TABLE table_name MODIFY COLUMN column_name datatype;

    Приклад:

    ALTER TABLE test_table MODIFY COLUMN id INT NOT NULL UNIQUE;

  5. Пропоную використовувати інструмент lsblk

    -n - не виводити шапку таблиці
    -i - приймає вказівку розділу, додаємо необхідний розділ після ключа /dev/s...
    --output - самостійна побудова таблиці виведення
    PKNAME - ім'я батьківського пристрою

    В результаті виконуємо команду:

    lsblk -n -i /dev/sda2 --output PKNAME

    Виведе:

    sda

  6. Python широко поширений у багатьох сферах: від системного адміністрування до Data Science.

    Веб розробка

    Найчастіше Python використовується в веб-розробці. Для роботи з ним підключають фреймворки: Tornado, Pylons, TurboGears, Flask, CherryPy і - найпопулярніший - Django.

    Існують і рушії для створення сайтів на Python:

    • Abilian SBE;
    • Ella;
    • Saleor;
    • Wagtail;
    • Django-CMS.

    Також на Python пишуть парсери для збору інформації в інтернеті.

    Програми

    Хоч мова і не компілюється, за допомогою неї все одно створюють десктопні програми. Ось, наприклад, що було розроблено на Python:

    • GIMP - візуальний редактор на Linux;
    • Ubuntu Software Center - центр застосунків в ОС Ubuntu (один з дистрибутивів Linux);
    • BitTorrent до 6 версії - менеджер торрент-закачувань (пізніше програму переписали на C++, але мережі peer-to-peer все ще працюють на Python);
    • Blender - програма для створення 3D-графіки.

    Мобільні застосунки

    Мобільна розробка на Python менш популярна. Для Android частіше пишуть на Java, C #, C++ або Kotlin, а для iOS - на Swift або Objective-C. На Python зазвичай програмують серверну частину програми. Наприклад, клієнт Instagram для iOS написаний на Objective-C, а сервер - на Python.

    Ігри

    Багато комп'ютерних ігор були повністю або частково написані на Python. Існує хибна думка, що ця мова не підходить для серйозних проєктів, але насправді вона використовувався в розробці таких хітів, як:

    • Battlefield 2;
    • World of Tanks;
    • Civilization IV;
    • EVE Online.

    Попри можливість реалізації призначеного для користувача інтерфейсу і роботи з графікою, на Python в основному пишуть скрипти - наприклад, взаємодії персонажів, запуску сцен, а також обробки подій.

    Вбудовані системи (embedded systems)

    На Python розробляють вбудовані системи для різних пристроїв. Наприклад, мова прижилася в Raspberry Pi (комп'ютер розміром з карту пам'яті).

    Ще проєкти з вбудованою системою на Python:

    • The Owl Embedded Python System;
    • Python Embedded Tools;
    • Embedded Python.

    Мова застосовується у вбудованих системах верстатів з ЧПУ, засобах автоматичного регулювання (температури, витрати рідини, тиску і так далі) і в телекомунікаційному обладнанні.

    Створення скриптів

    Python підходить для написання плагінів і скриптів до вже готових програмах. Наприклад, для реалізації ігрової логіки або створення додаткових модулів. Скрипти на цьому мови вбудовують і в програми на інших мовах, щоб автоматизувати будь-які завдання.

    Системне адміністрування

    Системним адміністраторам Python потрібен для автоматизації завдань. Він простий, потужний і підтримує спеціальні пакети, які підвищують його ефективність. І, найголовніше, він за замовчуванням встановлений на всі сервери з ОС Linux.

    Завдяки лаконічності Python можна швидко прочитати код і знайти слабкі місця. Форматування в мові - частина синтаксису.

    Наукові дослідження

    В Python є кілька бібліотек, які стануть в пригоді для проведення досліджень і обчислень:

    • SciPy - бібліотека з науковими інструментами;
    • NumPy - розширення, яке додає підтримку матриць і багатовимірних масивів, а також математичні функції для роботи з ними;
    • Matplotlib - бібліотека для роботи з 2D- і 3D-графікою.

    Завдяки бібліотекам і простоті освоєння мови багато вчених вибирають Python - особливо він популярний у математиків і фізиків.

    Data Science

    Python - один з найбільш використовуваних в Data Science мов. На ньому пишуть алгоритми програм з машинним навчанням і аналітичні програми. За допомогою нього обслуговують сховища даних і хмарні сервіси.

    Також він допомагає парсити дані з інтернету. Наприклад, в Google Python застосовують для індексації сайтів.


  7. До вищесказаного можна додати наступне. Далеко не всі пулл-реквести приймаються розробниками. Тут потрібно дотримати ряд правил:

    1. Пулл-реквест (ПР) повинен бути добре оформлений і містити вичерпний опис.

    2. Звичайне правило, один баг - один ПР, одна фіча - один ПР. Не потрібно намагатися запхати відразу купу всього.

    3. Дуже важливо дотримуватися Code Style того проєктe, для якого ви робите ПР. Нехай навіть він здається вам протиприродним (наприклад ви завжди робите відступи у вигляді 4 пробіли, а в проєкті таби).

    Не потрібно боятися робити ПР-и, адже допомогти можна навіть в дрібниці. Наприклад ви знайшли помилку перекладу в readme файлі або вам здається що якийсь опис фічі можна зрозумілішо перефразувати.

    На гітхабі мільйони проєктів, які живуть виключно на ентузіазмі творців, хороші ПР-и дуже добре стимулюють цей ентузіазм)


  8. Поясню на простому прикладі

    1. Крутий програміст створив репозиторій.
    2. Ви зробили форк його сховища (тобто скопіювали до себе).
    3. Ви зробили якісь круті зміни у своєму репозиторії.

    Тепер якщо ви хочете, щоб крутий дядько вніс ваші круті зміни у свій крутий код. І ви просите, щоб він взяв ваші зміни, тобто зробив git pull. Це і називається pull request


  9. Алгоритмічна рекурсія - це за визначенням алгоритм, побудований зі стратегії Розділяй та володарюй (Divide-and-Conquer), в якому для зберігання підзадач використана LIFO структура даних, тобто, попросту кажучи, стек.

    У таких алгоритмів натуральним чином є прямий хід, коли задача розбивається на дрібніші підзадачі, які заносяться в стек. Потім підзадачі витягуються зі стека по одній і вирішуються за таким же принципом, тобто розбиваються на ще дрібніші підзадачі. Розбиття триває до тих пір, поки підзадача не стане розв'язуватися тривіально.

    А також у рекурсивних алгоритмів є зворотний хід, коли все дрібніші підзадачі вже вирішені і їх рішення тепер можна об'єднати в рішення більше завдання.

    Наявність зворотного ходу (backtracking) - відмінна риса саме рекурсивних алгоритмів. Тобто в рекурсії завжди є зворотний хід. Ось, власне, "при чому тут рекурсія".

    Не всім рекурсивним алгоритмам потрібен цей зворотний хід - деяким алгоритмам просто нічого не потрібно робити на зворотному ході. Але сам процес зворотного ходу в рекурсивному алгоритмі завжди присутній, хай навіть і незримо.

    Тому якщо ви розглядаєте якийсь рекурсивний алгоритм (перебору або чого-небудь ще), то там завжди обов'язково буде і "повернення" (тобто зворотний хід). А вже як він використовується в вашому алгоритмі та чи використовується взагалі - залежить від алгоритму.

    У рекурсивному алгоритмі розставлення N ферзів на шахівниці, підзадачею є завдання розставлення  m ферзів що залишилися (m < = N), коли перші N - m ферзів вже якось розставлені. Зворотний хід в цьому алгоритмі використовується тоді, коли ми раптом з'ясували, що підзадача не має рішення - ми повертаємо цей результат на попередній рівень рекурсії в процесі зворотного ходу і тим самим говоримо йому, що треба спробувати якийсь інший варіант розставлення N - m перших ферзів. А також, якщо вам доручено знайти всі можливі розстановки (а не якусь одну), зворотний хід рекурсії буде використовуватися для тих же цілей незалежно від того,  чи успішно  вирішена підзадача, чи ні.