Изучаем Bash. Как не отправить все в /dev/null

В скрипте инициализации RHEL допущена ошибка, приводящая к удалению всех файлов

Не делать assert’ов в критических точках приложения – отличный способ выстрелить в ногу с рикошетом в голову.

Пример из новости выше:

   stop
rm -rf $SQUID_PIDFILE_DIR/*
start

Без контекста заметить проблему трудно, но точно известно если переменная SQUID_PIDFILE_DIR не будет определена мы выполним:

   rm -rf /*

Веселого в этом мало, a для того чтобы избежать таких ситуаций (которые могут легко возникнуть в большом BASH скрипте) нужно было использовать следующую проверку:

   stop
[ -z "$SQUID_PIDFILE_DIR" ] || exit 42
rm -rf $SQUID_PIDFILE_DIR/*
start

При таком подходе мы бы никогда не вызвали команду rm на корне файловой системы.

Про контрактное программирование на Java можно почитать здесь.

Git Bash for Windows

В целом командная строка под оффтопик это страх и ужас. sh.exe конечно же спасает, но отсутствие тесной интеграции с системой (в linux можно делать все из консоли) создает неудобства. В общем пользователям git bash for win посвящается: запускаем несколько окон с гитом в нужных проектах из bat файла:

rem git-win.bat
cd C:\dev\projects\project1
start "" "%SYSTEMDRIVE%\Program Files (x86)\Git\bin\sh.exe" --login -i

cd C:\dev\projects\project2
start "" "%SYSTEMDRIVE%\Program Files (x86)\Git\bin\sh.exe" --login -i

Если у вас 32-битная система(интересно, остались еще такие?) то этот файл

rem git-win-x86.bat
cd C:\dev\projects\project1
start "" "%ProgramFiles%\Git\bin\sh.exe" --login -i

cd C:\dev\projects\project2
start "" "%ProgramFiles%\Git\bin\sh.exe" --login -i

Geek Cup

geek_cup

1. Одинаковые шестнадцатеричные цифры представлены одной и той же буквой:

(DRINKCOFFEE << F) + (BEHAPPY << D) = ITRANSITION

2. Предыдущее поколение в “Game of Life” с минимальным количеством живых клеток:

□□□□□□□□□□□□□□□□□□□□
□□■□□■□□□■□□■□□■□□■□
□■□□■□■□■□□■□□■□□■□□
□■□□■□■□■■□■■□■■□■■□
□■□□■□■□■□□■□□■□□■□□
□□■□□■□□■□□■□□□■□□■□
□□□□□□□□□□□□□□□□□□□□

3.

puts ->(v){r,c,l=0<=>v.abs,1,->(n,g;r,c){g>=~-n||g<=1?1:->(){r,c=1,2;->(){->{r=r+l[n-g,c]if(c+g<=n)}[];c+=1;}[]while(c<=g);r}[];};r+=l[v,c]and(c=c+1)while(c-v<=0)l=->(l){l+=1}[r]}[0xc0ffee]

Первые грабли Java 8

java-prof-by

Моя первая попытка выступить на Java Professionals BY: Meetup #2 вышла комом, с самого начала все не заладилось и я смог победить волнение и кашу в голове только к концу доклада. Опыт получен, и надеюсь аудитория поняла и простила меня 😀

10341492_793072000739301_8268639565730347492_n

На самом деле проблем в Java 8 нету 😉 Есть проблемы перехода с предыдущих версий языка, а остальное – проблемы неправильного использования.

Bug or Feature?

К выпуску Java 8 Oracle предоставила гайд: Compatibility Guide for JDK 8, но также надо помнить, что переходя с Java 6 на Java 8 нужно учитывать также несовместимости 6 и 7 версии (тем кто верит, что Java на 100% обратно совместима лучше не открывать ссылку) и так далее.

Что же должно волновать обычного разработчика, при переходе с 7 на 8 Java?

Пофикшенные баги наподобие этих:

JDK-6559590 – изменилось поведение String.split(“”), как следствие в некоторых случаях можно словить ArrayIndexOutOfBoundsException, а что еще хуже в тихую закораптить данные (например не используя первый символ, который всего был пустой строкой).

user@r2d2:~$ cat Test.java 
import java.util.Arrays;

public class Test {

    public static void main(String[] args) {
        System.out.println(Arrays.toString("123".split("")));
    }
}
user@r2d2:~$ /usr/lib/jvm/java-8-oracle/bin/javac Test.java
user@r2d2:~$ /usr/lib/jvm/java-8-oracle/jre/bin/java Test
[1, 2, 3]
user@r2d2:~$ /usr/lib/jvm/java-7-oracle/bin/javac Test.java
user@r2d2:~$ /usr/lib/jvm/java-7-oracle/jre/bin/java Test
[, 1, 2, 3]

JDK-8021591 – в коллекциях CopyOnWriteArrayList/CopyOnWriteArraySet/AbstractCollection методы removeAll(Collection)/retainAll(Collection) не кидали NPE при передачи в них null. Как следствие перейдя с 4,5,6,7 на 8-ку можно ловить NPE пачками.

// AbstractCollection.java
// Java 7 ---------------------------------------- / Java 8 ----------------------------------
public boolean removeAll(Collection<?> c) {        public boolean removeAll(Collection<?> c) {
                                                  >    Objects.requireNonNull(c);
    boolean modified = false;                          boolean modified = false;
    Iterator<?> it = iterator();                       Iterator<?> it = iterator();
    while (it.hasNext()) {                             while (it.hasNext()) {
        if (c.contains(it.next())) {                       if (c.contains(it.next())) {
            it.remove();                                       it.remove();
            modified = true;                                   modified = true;
        }                                                  }
    }                                                  }
    return modified;                                   return modified;
}                                                  }

// Same in retainAll method

Default methods

Должны использоваться аккуратно! Нужно понимать, что цель default методов в возможности расширения интерфейса, без потери совместимости со старым кодом:

Default methods enable new functionality to be added to the interfaces of libraries and ensure binary compatibility with code written for older versions of those interfaces.

What’s New in JDK 8

Использование default методов не по назначению может привести к излишнему усложнению кода:

public class DefaultMethods {

    public static void main(String[] args) {

        // WTF is happens here!?
        Tiger tiger = new Tiger();
        tiger.meow();
        tiger.sayMeow();
        tiger.saySuperMeow();

        Cat cat = new Tiger();
        cat.meow();
        cat.sayMeow();
    }
}

class Cat implements Feline {
    public void sayMeow() {
        meow();
    }
}

class Tiger extends Cat implements Predator {
    public void saySuperMeow() {
        super.meow();
    }
}

interface Feline {
    default void meow() {
        System.out.println("Meow");
    }
}

interface Predator extends Feline {
    default void meow() {
        System.out.println("Rrrr");
    }
}

Разобраться в этой жести нам помогут простые правила (которые описаны в jsr335-final/spec/H.html):

  • A concrete implementation in the class wins
  • The lowest implementation in the implemented interfaces wins
  • If there are multiple implementations available through different interfaces that are on different paths up through the class hierarchy, the program doesn’t compile

Оставлю возможность понять что тут происходит вам самим. Для тех кому лень запускать ниже приведен вывод:

Rrrr
Rrrr
Meow
Rrrr
Rrrr

Executing Streams in Parallel

Определенно Stream API это крутое нововведение в Java 8. Особенно многим понравилось возможность “ускорить” вычисления используя параллельные стримы. К сожалению в действительности это все не так однозначно, и сейчас я это продемонстрирую:

import java.util.stream.IntStream;

public class JavaStreams {

    public static void main(String[] args) {
        test();
        test();
        test();
    }

    public static void test() {

        new Thread(() -> {
            long start = System.currentTimeMillis();

            long g = IntStream
                    .range(1, 100)
                    .map(x -> x * 2)
                    .sum();

            long stop = System.currentTimeMillis();
            System.out.println("Run1. Execution time: " + (stop-start) + "ms. Result: " + g);
        }).start();

        new Thread(() -> {
            long start = System.currentTimeMillis();

            long g = IntStream
                    .range(1, 100)
                    .map(x -> {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                        }
                        return 42;
                    })
                    .sum();

            long stop = System.currentTimeMillis();
            System.out.println("Run2. Execution time: " + (stop-start) + "ms. Result: " + g);
        }).start();


    }

}

Симулируем типичное веб приложение: к нам приходят пользователи и выполняют какие-то операции в параллельных потоках. Без использования параллельных стримов мы получаем такие результаты:

Run1. Execution time: 7ms. Result: 9900
Run1. Execution time: 9ms. Result: 9900
Run1. Execution time: 0ms. Result: 9900
Run2. Execution time: 9919ms. Result: 4158
Run2. Execution time: 9921ms. Result: 4158
Run2. Execution time: 9915ms. Result: 4158

Очевидно что 9 секунд на отдачу результата во втором случае это очень много. Давайте применим параллельные стримы:

import java.util.stream.IntStream;

public class JavaStreams {

    public static void main(String[] args) {
        test();
        test();
        test();
    }

    public static void test() {

        new Thread(() -> {
            long start = System.currentTimeMillis();

            long g = IntStream
                    .range(1, 100)
                    .parallel()
                    .map(x -> x * 2)
                    .sum();

            long stop = System.currentTimeMillis();
            System.out.println("Run1. Execution time: " + (stop-start) + "ms. Result: " + g);
        }).start();

        new Thread(() -> {
            long start = System.currentTimeMillis();

            long g = IntStream
                    .range(1, 100)
                    .parallel()
                    .map(x -> {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                        }
                        return 42;
                    })
                    .sum();

            long stop = System.currentTimeMillis();
            System.out.println("Run2. Execution time: " + (stop-start) + "ms. Result: " + g);
        }).start();


    }

}

Run1. Execution time: 8ms. Result: 9900
Run1. Execution time: 21ms. Result: 9900
Run1. Execution time: 4936ms. Result: 9900
Run2. Execution time: 5024ms. Result: 4158
Run2. Execution time: 5013ms. Result: 4158
Run2. Execution time: 5026ms. Result: 4158

Замечательно! С 9 секунд до 5! Отличный результат. Стойте, WAT?

Wat

4936ms? WAT? Запустим еще раз:

Run1. Execution time: 9ms. Result: 9900
Run1. Execution time: 9ms. Result: 9900
Run1. Execution time: 28ms. Result: 9900
Run2. Execution time: 4308ms. Result: 4158
Run2. Execution time: 4921ms. Result: 4158
Run2. Execution time: 5510ms. Result: 4158

И еще один разок:

Run1. Execution time: 6ms. Result: 9900
Run1. Execution time: 11ms. Result: 9900
Run2. Execution time: 5016ms. Result: 4158
Run1. Execution time: 5533ms. Result: 9900
Run2. Execution time: 5621ms. Result: 4158
Run2. Execution time: 6418ms. Result: 4158

Не ну все понятно скажете вы – мы же загрузили процессор второй задачей, вот он бедный с первой не справляется.

Ок, тогда давайте скажем первому стриму выполняться последовательно, а второму продолжать выполняться в параллели, несколько запусков и типичный результат:

Run1. Execution time: 9ms. Result: 9900
Run1. Execution time: 1ms. Result: 9900
Run1. Execution time: 1ms. Result: 9900
Run2. Execution time: 5016ms. Result: 4158
Run2. Execution time: 6825ms. Result: 4158
Run2. Execution time: 6916ms. Result: 4158

fry

Как же работают стримы внутри? Заглянем в имплементацию Stream, а точнее BaseStream, которая представлена классом AbstractPipeline. В нем есть метод parallel() в котором выставляется внутренне свойство parallel в true и возвращается стрим. Где же используется данное свойство parallel? Как мы знаем стримы ленивые и если в стриме не будет указан терминальная операция, то он не будет выполняться. Какие мы знаем терминальные операции? Самая очевидная операция это reduce, которая вызывает метод AbstractPipeline#evaluate передавая туда операцию ReduceOps. Соответственно перед выполнением в зависимости от свойства parallel стрим будет выполняться либо параллельно либо последовательно. Итак наша дорога через интерфейс TerminalOp пришла к одной из реализаций: ReduceOp и методу evaluateParallel, который возвращает ReduceTask#invoke#get. Сам ReduceTask – является ForkJoinTask который будет выполняться на ForkJoinPool. Но мы же не создаем свой пул, какой пул используется? А используется ForkJoinPool#commonPool(размер которого равен количеству процессоров минус 1), который один на всех. Собственно в этом и корень всех проблем – долгох живущие параллельные стримы “съедают” пул и не дают другим параллельным стримам выполниться.

Что делать? Не использовать parallel stream в многопользовательских приложениях и для маленьких задач.

Q. Как запустить стрим в своем пуле?
A. Создать свой ForkJoinPool и отправить на выполнение таск с Stream:

ForkJoinPool forkJoinPool = new ForkJoinPool(2);
forkJoinPool.submit(() ->
    //parallel task here, for example
    IntStream.range(1, 1_000_000).parallel().filter(PrimesPrint::isPrime).collect(toList())
).get();

Q. Одна из библиотек внутри использует parallel stream, что делать?
A. Писать разработчикам библиотеки и бить их по пальцам. Конечно можно ограничить пул: -Djava.util.concurrent.ForkJoinPool.common.parallelism=1, но это только усугубит ситуацию. Конечно можно и увеличить пул, в таком случае производительность будет низкая но более предсказуема. В целом я не нашел способа глобально запретить стримам использовать параллелизм.

Поиграемся с размером пула:

parallelism=1

Run1. Execution time: 13ms. Result: 9900
Run1. Execution time: 4924ms. Result: 9900
Run1. Execution time: 4918ms. Result: 9900
Run2. Execution time: 5018ms. Result: 4158
Run2. Execution time: 9919ms. Result: 4158
Run2. Execution time: 13548ms. Result: 4158

parallelism=10

Run1. Execution time: 12ms. Result: 9900
Run1. Execution time: 18ms. Result: 9900
Run2. Execution time: 2011ms. Result: 4158
Run1. Execution time: 2022ms. Result: 9900
Run2. Execution time: 2311ms. Result: 4158
Run2. Execution time: 2431ms. Result: 4158

parallelism=100

Run1. Execution time: 91ms. Result: 9900
Run1. Execution time: 118ms. Result: 9900
Run1. Execution time: 111ms. Result: 9900
Run2. Execution time: 414ms. Result: 4158
Run2. Execution time: 399ms. Result: 4158
Run2. Execution time: 382ms. Result: 4158

parallelism=1000

Run1. Execution time: 333ms. Result: 9900
Run1. Execution time: 356ms. Result: 9900
Run1. Execution time: 362ms. Result: 9900
Run2. Execution time: 439ms. Result: 4158
Run2. Execution time: 475ms. Result: 4158
Run2. Execution time: 468ms. Result: 4158

parallelism=10000

Run1. Execution time: 365ms. Result: 9900
Run1. Execution time: 448ms. Result: 9900
Run1. Execution time: 450ms. Result: 9900
Run2. Execution time: 542ms. Result: 4158
Run2. Execution time: 535ms. Result: 4158
Run2. Execution time: 543ms. Result: 4158

Все тесты выполнялись на Intel(R) Core(TM) i5-3317U CPU @ 1.70GHz

Чтобы далеко не ходить оставлю ссылку на доклад второго спикера “Алексей Руцкой: Как развиваться Java-разработчику

На этом спасибо за внимание и я всегда рад вашим комментариям 🙂

Задача про Паровозики

Задачу эту я услышал сегодня на Ciklum Java Saturday (мини-конференция).

Итак

locomotive

У нас имеется бесконечная координатная прямая. На ней два паровоза и между ними на неизвестном расстоянии находится станция.

Каждый паровоз имеет свой процессор и исполняет команды:

  • L – передвинуться в левую ячейку
  • R – передвинуться в правую ячейку
  • if (station) {L/R/goto(n)} – если поезд находится на станции то выполнить единственную команду команду
  • goto(n) – перейти на выполнение n-ой строки исходного кода

Задача: столкнуть два паровоза используя данные команды. При этом программа одна и та же для каждого паровоза т.е. вы пишете одну программу, которая исполняется параллельно на обоих паровозах с одинаковой частотой.

Пример программы:

1. L
2. R
3. if (station) {goto(4)}
4. goto(1)

Которая ничего не делает 🙂

===========================
Решение:

1. L
2. if (station) {goto(4)}
3. goto (1)
4. L
5. goto (4)

===========================

upd. Аккуратно, в комментариях тоже есть решение 🙂

Изучаем GIT – the stupid content tracker


Вы наверно знаете что такое СУВ(VCS) и даже если вы ещё не начали использовать системы контроля версий, этот пост для вас.

Пожалуй стоит начать с выбора VCS. Как и многие другие для себя я выбрал Git(читается как ‘Гит’), распределенную систему управления версиями, которую написал Линус Торвальдс и Джунио Хамано с поддержкой сообщества.

Почему Git? Для начала послушайте или почитайте, что говорит о Git сам Линус. Также стоит обратить внимание на популярность системы. Сам для себя я невольно выбрал Git около 5-ли нет назад, когда посмотрел это видео.

Ну что же. Прежде чем читать тысячи мануалов и десятки книг, предлагаю ознакомиться с терминологией, она в разы повысит понимание материалов.

На данный момент вы уже должны понимать что такое Git и с чем его едят. Иметь представление чем оперирует система и как её можно использовать. Пора начать получать реальные знания!

Первое что я рекомендую прочитать: Pro Git и Волшебство Git. Их большой плюс для начинающего – русский язык. Далее можно пробежать по статьям на хабре и попробовать что-нибудь из этого.

Надо заметить, что вы на этом этапе должны научиться использовать Git и все что осталось – привыкнуть и ввести в обиход.

Если же вы хотите изучить гит полностью и на английском, то для вас есть несколько ссылок:

  1. Loeliger J. – Version Control with Git [2009, PDF, ENG]
  2. The Pragmatic Programmers – Swicegood T. – Pragmatic Version Control Using Git [2008, PDF, ENG]
  3. The Pragmatic Programmers – Swicegood T. – Pragmatic Guide to Git [2010, PDF, ENG]
  4. [TekPub] Mastering Git [2010, Video, ENG]

А также честно купить

И на последок:
Git Flow
Удачная модель ветвления для Git [EN]
Командная работа в Git
Постигаем Git
gitfm – персональные рекомендации, на основе вашего профиля в GitHub
Learn Git Branching
Интерактивный справочник команд
Кишки Git
GitHub Help
Git Tower.

Git-Master Ibragimov Ruslan

Серия курсов на code school:
Try Git – Базовые умения
Git Real – Необходимый минимум
Git Real 2 – Продвинутый уровень: interactive rebase, filter-branch, stashing, submodules, reflog
Mastering GitHub – Нацелен на обучение коллаборации

piwik vs adblock

Отказавшись от Google Analitycs (GA) в пользу собственного инстанса piwik я получил больше контроля над статистикой, а также не делюсь информацией о своих посетителях с кем-либо.

У GA есть существенный недостаток (для владельца ресурса), он успешной блокируется AdBlock’ом и многими другими плагинами. Я сам пользуюсь AdBlock’ом, и готов делиться статистикой с владельцами ресурсов, но не с третьими лицами.

Собственно Piwik также обладает таким недостатком: его стандартные пути добавлены в фильтры многих банерорезок, но в данном случае это ограничение можно обойти.

Что можно сделать, чтобы уйти из под фильтров? Правильно, изменить имена файлов.

Я решил эту проблему с помощью конфига nginx. Я пока только изучаю его работу, и может пользуюсь не самым правильным способом, но способ работает 😉

Имеется конфиг сервера


    server {
        server_name piwik.domain.com;

        ...
    }

Удобный субдомен, но AdBlock так не считает. Что ж, поменяем его на другой:


    server {
        server_name piwik.domain.com pw.domain.com;

        ...
    }

Отлично, теперь осталось изменить имена файлов:


    server {
        server_name piwik.domain.com pw.domain.com;

        location = /jquery.min.js {
                 rewrite ^ /piwik.js;
        }

        location = /jquery.min.php {
                 rewrite ^ /piwik.php;
        }

        ...
    }

Теперь в сниппете который предоставляет piwik нужно заменить piwik.domain.com на pw.domain.com, piwik.js на jquery.min.js, а piwik.php на jquery.min.php.

Пользуйтесь данными знаниями во благо 😉

p.s. Данный подход можно легко забанить. Взгляните на awstats и другие утилиты которые парсят логи для 100% подсчета посетителей.

Попасть в Литовское посольство в Минске бесплатно

Хотите в Литву, но не получается зарегистрироваться? Читаем топик на форуме онлайнера(на котором за публикацию данного материала меня успешно забанили), прочитали? Руками не хочется работать? Ок, вот два видео, в которых объясняется как упростить себе жизнь и усложнить барыгам.

И второе видео, в котором еще больше упрощается жизнь (Сообщайте о своих успехах/неудачах в комментариях тут или на youtube)

Скрипт для автоматического обновления календаря

Enjoy!

А теперь мои размышления на тему открытого доступа к скрипту

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

Я же считаю, что открытие информации всегда улучшает положение.

А теперь давайте рассмотрим данную ситуацию:

Введем роли:

А: Обычный пользователь, который ничего не знает о данном скрипте
Б: Обычный пользователь, который знает о данном скрипте
В: Обычный пользователь, который знает о данном скрипте но не умеет им пользоваться
Г: Нарушитель, который не пользуется скриптами
Д: Нарушитель, который пользуется полу-автоматическими скриптами
Е: Нарушитель, который пользуется автоматическими скриптами
Ж: Нарушитель, который в отсутствие заказов регистрирует левых пользователей
З: Человек, который желает стать нарушителем

Скрипт (который описывается в видео) – относится к полу-автоматическим скриптам.
Полу-автоматический скрипт – требует постоянного вмешательства пользователя для работы, т.е. в данном случае это проход по ссылке, ввод данных и капчи, смена IP.
Автоматический скрипт – не требует постоянного вмешательства, используется прокси-сервера для смены IP, китайцы и школьники для разгадывания капчи, с помощью таких скриптов можно регистрировать одновременно много человек.
Нарушитель – человек, который берет деньги(чаще всего в черную) за услуги, которые должны быть легко доступны и бесплатны, либо мешает работе посольства

Думаю с терминологией которую я ввел разобрались.

Теперь замечу, что полу-автоматический скрипт, коим является описанный выше, не может значительно повысить кол-во талонов, который может взять один человек, в то же время, он отлично подходит для личного использования, т.е. для того, чтобы взять талон себе и намного менее подходит чтобы взять талон 100 клиентам нарушителя.

Теперь пойдут возможные случаи, будем рассматривать появление в открытом доступе описанного скрипта.

Случай А.

Честно говоря мне хочется верить, что в перспективе для него ситуация станет лучше. Т.к. Пользователи категории Б и В не пойдут покупать талоны, а следовательно спрос будет меньше. Это рынок, и спрос рождает предложение. Да, получится так что изначально было A = Б = В, а после появления станет А < Б и А <= В. Т.е. вероятнее всего, что А могут пострадать от действия скрипта. В тоже время хочу заметить, что и на данный момент ситуация у А,Б,В плохая и часто они не могут взять сами талон и обращаются к нарушителям.

Случай Б.

Это целевая аудитория 🙂 В теории Б смогут сами и легче получить талоны, следовательно уменьшат спрос на услуги нарушителей -> уменьшится цена на услуги, и многие прекратят заниматься таким промыслом (предположительно Г,Д)

Случай В.

Средний случай между А и Б. Если человек попросит помощи у категории Б, то он сам перейдет в эту категорию, в противном случае его можно причислить к категории А.

Случай Г.

Отличное знание механизма регистрации, отлаженная схема и набитая рука, вот что позволяет этой категории нарушителей успешно или не очень существовать. Появление скрипта может упростить им задачу и они перейдут в категорию Д, либо получится, что они потеряют клиентов категории Б,В.

Случай Д.

Для них появление скрипта станет увеличением конкуренции, т.к. пользователи категории Б не обратятся к ним, а также часть из Г перейдет в данную категорию.

Случай Е.

Этот случай для меня тяжелый, я не могу сказать насколько успешно работают такие нарушители, и сколько вообще их может существовать (могу предположить, что их единицы). Скорее всего такие колебания их не затронут, все-таки они работают быстрее и стабильнее других категорий нарушителей и пользователей. В тоже время, скрипт значительно поможет конкурировать пользователям Б с этой категорией.

Случай Ж.

Приравниваю к категории Е, с пометкой – что это те еще пдрсы.

Случай З.

Оставил на последок, как самый часто задаваемый мне вопрос: “а что если барыги возьмут скрипт и начнут рубить бабло”. Если они возьмут скрипт, то не смогут повлиять сильно, в следствии характера скрипта. А чтобы сделать из него автоматический скрипт, нужно намного больше знаний, так что на количество людей категории Е,Ж больше не станет.

Выводы

Это мой взгляд на ситуацию. Я основывался на том, что категория Е забирает около 50% талонов в день, и то что данный скрипт поможет конкурировать с такими автоматчиками категории А.

Также очень много зависит от количества в каждой категории.

Но, т.к. за первый час несколько людей уже получили талоны используя скрипт, я думаю всё только к лучшему 🙂