Блог пользователя Zip753

Автор Zip753, 12 лет назад, По-русски

Ребята, со мной творится что-то неладное! Собственно, в чём это выражается? А выражается это в моих тупейших багах при написании кода. Сейчас самая актуальная и самая обидная среди них — выделение малого количества памяти. Смешно же, правда?

Всё началось с Зимней школы этого года, где я долго не мог понять, почему же наше решение даёт WA#29. Оказалось, я перепутал выделение памяти в квадратной динамике (что-то вроде a[5010][10010] вместо a[10010][5010]). Сами понимаете, потерять задачу на такой глупости — очень обидно не только для меня, но и для всеё команды (благо они меня не запинали). Второй случай был на той же школе, ровно с той же самой ошибкой. Правда тестирование немного подзатянулось, поэтому я увидел ужасающий список из Access Violation (или что-то в этом роде) только за 2 минуты до конца контеста. Последние 2 случая были вчера, на 6-ом раунде COCI. У меня было приятнейшее идейное прозрение по 5-ой задаче, но к моему удивлению оно набрало 84 балла. Когда я открыл протокол, то увидел кучу обращений к несуществующей памяти. И что вы думаете? Я опять-таки выделил массив 1000*1000, когда надо было по 10 раз больше на каждое измерение (правда там нужна была линейная оптимизация по памяти, но это заняло у меня 3 минуты).

И вот, стоя сегодня в душе и обдумывая написание этого поста, я с ужасом осознал, что 4-ая задача упала по той же причине. Я вылетел из ванной, дописал двойку, отправил и получил свои заслуженные 80 баллов вместо 20 (хоть это и не спасло меня от лимита по времени на двух тестах). Это было последней каплей моего всего, вот я и решил обратиться к вам за помощью.

Я действительно не знаю, что мне надо сделать, чтобы эти тупобаги исчезли. У меня и раньше бывали с таким проблемы, но они постепенно исчезали — я становился более внимательным что-ли. А вот это вот ну уж совсем непостижимо для меня. Если идейная тупость зависит от опыта решения задач и участия в соревнованиях, то от чего зависит моя тупость?

Мне не страшно, если меня заминусуют или если я пишу какую-то глупость, потому что для меня это критично важно, ибо через неделю уже УОИ, а у меня такая трабла.

  • Проголосовать: нравится
  • +12
  • Проголосовать: не нравится

»
12 лет назад, # |
Rev. 2   Проголосовать: нравится +2 Проголосовать: не нравится

На Java такой фигни не бывает, инфа 100%

Конкретно от неверных размеров массивов может спасти vector<vector<int> >

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +1 Проголосовать: не нравится

    Вектора спасают не всегда.

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится +3 Проголосовать: не нравится

      Чтобы vector всегда выдавал RE при выходе за границы, можно или пользоваться методом at, или переопределить operator[] :

      template <class T>
      struct MyVector : vector<T> {
        T &operator [] ( int i ) {
          assert(0 <= i && i < (int)vector<T>::size());
          return vector<T>::operator[](i);
        }
      };
      
      • »
        »
        »
        »
        12 лет назад, # ^ |
        Rev. 2   Проголосовать: нравится +23 Проголосовать: не нравится

        …а при использовании GCC можно просто определить один макрос _GLIBCXX_DEBUG.

        http://gcc.gnu.org/onlinedocs/gcc-4.7.2/libstdc++/manual/manual/bk01pt03ch17s03.html

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится

          Я мечтаю о подобном макросе для plain-массивов. Вот умеет же Delphi проверять подобное. И valgrind вроде умеет. Я поэтому долгое время писал TopCoder в Borland C++ Builder-е — у него есть режим Code Guard, который в рантайме проверяет все что можно.

          • »
            »
            »
            »
            »
            »
            12 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится

            На самом деле такая библиотека есть для GCC, называется Mudflap (-fmudflap -lmudflap). Ловит, например, такие ошибки:

            int arr[10];
            
            arr[10] = 42;

            Однако эта библиотека, увы, склонна давать ложные срабатывания.

      • »
        »
        »
        »
        12 лет назад, # ^ |
        Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

        При таком переопределении оператора обращение к элементу за O(size) будет происходить :) (Извините за некропостинг, просто тема актуальная))

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится

          Почему? vector<T>::operator[] и vector<T>::size() оба работают за O(1).

          • »
            »
            »
            »
            »
            »
            12 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится

            Странно... Всю жизнь считал, что вектор умеет считать размер только за O(size). Спасибо за инфу!

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
            Проголосовать: нравится 0 Проголосовать: не нравится

          Уже сказали, что O(1). Можно даже уточнить, изнутри v.size() устроен так:

          size_type size() const { return size_type(end() - begin()); }
          

          Поэтому в частности

          n = v.size();
          for (i = 0; i < n; i++)
          

          быстрее, чем

          for (i = 0; i < v.size(); i++)
          

          P.S. Еще быстрее вот этот код:

          for (i = v.size() - 1; i >= 0; i--)
          
»
12 лет назад, # |
Rev. 2   Проголосовать: нравится +21 Проголосовать: не нравится

Мне помогает такое: когда уже кажется, что задача написана и надо посылать, вместо посылки посидеть ещё минут пять и перечитать весь код. Надо натренировать себя за эти пять минут просмотреть (и не один раз) все, казалось бы, наименее значительные места, как-то: эти самые размеры массивов, индексы, использование именно тех переменных, что надо и т.п.

Для избежания завалов на размерах массивов можно ещё вначале определять все ограничения как именованные константы, и потом использовать только их. Ну и ещё хороший, но дорогой по времени приём — позвать товарища по команде и рассказать ему, как работает код.

»
12 лет назад, # |
  Проголосовать: нравится +5 Проголосовать: не нравится

хорошим способом является выделять не массив на maxN памяти, а динамически выделять (std::vector) ровно n памяти. Тогда решение не будет работать и на маленьких тестах (скорее всего) + если запустить в режиме debug, то будет выполняться проверка того, что индекс попадает в границы [0, size)

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +5 Проголосовать: не нравится

    Вектора довольно временеёмкие и далеко не всегда удобные. Мне кажется, что речь не о конкретном баге с выделением памяти, а о банальных ошибках вообще.

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      По времени они почти никогда не мешают. За исключением случаев, когда нужно что-то упихнуть в TL.

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      "Вектора довольно временеёмкие и далеко не всегда удобные" — категорически не согласен, разве что если вы не пишите какую-нибудь 6-ти мерную динамику. У векторов медленная переаллокация, но если сразу заресайзить как надо, разницы нет.

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится +8 Проголосовать: не нравится

        Разница с массивом всё-таки есть, но очень маленькая (на практике незаметна).

      • »
        »
        »
        »
        12 лет назад, # ^ |
        Rev. 2   Проголосовать: нравится +11 Проголосовать: не нравится

        Приведу другой пример. У нас есть разреженный ориентированный граф. У каждой вершины степень ровно два.

        Первый способ хранить граф: список ребер.

        Второй способ хранить граф: массив векторов, у каждого вектора сделали .reserve(2)

        Результаты тестирования: у меня на компе вектор в ~7.5 раз медленней для графов от 106 до 107 вершин. По ссылке можно посмотреть, как я тестил.

        UPD: Для степени вершины 1 получаем разницу в ~10 раз.

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
            Проголосовать: нравится +10 Проголосовать: не нравится

          А массив [N][2] нельзя взять?

          • »
            »
            »
            »
            »
            »
            12 лет назад, # ^ |
              Проголосовать: нравится +4 Проголосовать: не нравится

            Можно :-) Я конкретизировал задачу, чтобы проще было тестировать, сравнивать время работы.

            На самом деле имеется в виду ситуация, с которой все мы сталкивались на практике: дан произвольный орграф из 106 вершин и 2·106 ребер.

            • »
              »
              »
              »
              »
              »
              »
              12 лет назад, # ^ |
              Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

              Ну тут же не в векторах проблема, а в способе хранения, http://pastie.org/private/enhuosaruijat5aavzefua так тоже быстро работать будет.

              То есть логично, что выделение 10^6 кусков памяти в куче, а затем рандомный доступ к ним, будет медленнее :)

              • »
                »
                »
                »
                »
                »
                »
                »
                12 лет назад, # ^ |
                  Проголосовать: нравится +12 Проголосовать: не нравится

                Да, ты прав, я не о том, что вектор устроен не оптимально.

                Мой пример вот о чем. Есть два часто используемых способа хранить граф — "массив векторов" и "список ребер". И иногда люди сперва пишут "массив векторов" и получают TL, а потом "переписал без векторов, зашло".

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится +7 Проголосовать: не нравится

      Тут уж выбор ваш.

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

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

»
12 лет назад, # |
  Проголосовать: нравится +32 Проголосовать: не нравится

Просто не едь на УОИ.

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +6 Проголосовать: не нравится

    Даже если я не поеду на УОИ, вряд ли это как-то повлияет на количество багов в коде.

    P.S. А нетушки вам :Р

»
12 лет назад, # |
  Проголосовать: нравится +27 Проголосовать: не нравится

А если сгенерить рандомный макс-тест и проверить, что не падает по RE и что при увеличении размера массива в 2-3 раза, ответ не изменится? Мне вроде помогает :-)

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +3 Проголосовать: не нравится

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

    А по теме поста — правильно было указано ниже. На УОИ времени должно быть более чем достаточно даже для того, чтобы проверить "список ошибок". И это довольно неплохая идея.

»
12 лет назад, # |
  Проголосовать: нравится +6 Проголосовать: не нравится

Говорят, есть такой метод — решать, решать, еще решать, писать соревнования, после соревнований видеть у себя такие ошибки и думать "я идиот, как можно делать такие глупости, больше никогда не буду", снова решать, решать, писать очередные соревнования, снова допускать на них аналогичные ошибки и думать примерно то же, что и в предыдущем случае... Идея базируется на 2 предположениях:

1) Не ошибается только тот, кто ничего не делает. 2) Мы учимся на своих ошибках.

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +3 Проголосовать: не нравится

    Спасибо огромное, это, пожалуй, именно то, что я хотел услышать в ответ. Я тоже думал об этих двух пунктах, они у меня постоянно в голове вертятся теперь, но мне казалось, что такая банальщина — это уж слишком. Что ж, буду 1)делать и 2)учиться. (:

    • »
      »
      »
      12 лет назад, # ^ |
      Rev. 2   Проголосовать: нравится +3 Проголосовать: не нравится

      У меня на NEERC был баг: локальная и глобальная переменная c одним и тем же именем s, но разным смыслом. Почему-то это давало далекий ВА, и поиск отнял кучу времени. Не уверен на 100%, но вроде мы таки сдали задачу, иначе я бы повесился. Выход в такой ситуации не в том, чтобы перечитывать код, а в том, чтобы уменьшить вероятность подобного рода ошибок. В данном примере правильнее приучить себя давать более осмысленные имена переменным, чем перечитывать сто раз. Каждый раз когда делаешь цикл, должна проходить проверка на невыход за границу, с опытом это происходит где-то в спинном мозге, без участия головного и без переключения на это внимания. Кроме того считаю правильным делать массивы с запасом. n <= 100 ? тогда пусть будет 107, 10^5? тогда (1 << 17). Ну и для борьбы с любыми багами описками полезнее писать с большей концентрацией, не торопясь настолько что допускаешь банальные ошибки на уровне описок.

  • »
    »
    12 лет назад, # ^ |
      Проголосовать: нравится +6 Проголосовать: не нравится

    Можно еще попробовать записывать все ошибки в список и перед сабмитом проверять их по списку. На IOI-style очень помогает.

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится +3 Проголосовать: не нравится

      Пробовал и такое когда-то делать — получился довольно внушительный список + не ясно толком, в каком порядке их лучше записывать, ибо всё время клинит на разных вещах. А проверять по такому громоздкому списку зачастую неудобно. Но я всё равно попробую ещё раз (:

  • »
    »
    12 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится +13 Проголосовать: не нравится

    А еще, есть клевый метод. Решать задачки на тимусе, или на CF (не во время контестов) и... не тестить у себя на компе. В качестве большего хардкора можно и не компилировать. Так можно неплохо приспособиться почти всегда писать с первого раза на АС.

    • »
      »
      »
      12 лет назад, # ^ |
        Проголосовать: нравится +41 Проголосовать: не нравится

      Следующий level после "не компилить" — писать код с закрытыми глазами. Потом есть еще один — не читать условие (только название задачи и пример)... Всем удачи :-)

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится -17 Проголосовать: не нравится

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

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится

        Т.е., все те, кто переходит к уровню "не читать условие" до того, как одолел уровень "писать с закрытыми глазами" — читеры и проходят не в том порядке?:)

      • »
        »
        »
        »
        12 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится

        А что делать если эти все level'ы идут в обратном порядке?

        • »
          »
          »
          »
          »
          12 лет назад, # ^ |
            Проголосовать: нравится -6 Проголосовать: не нравится

          Быть Бенджамином Баттоном, радоваться жизни