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

Автор ifsmirnov, история, 8 лет назад, По-русски

После начала обсуждения здесь я решил вынести его в отдельный тред.

Есть много разных тестирующих систем, и во всех свои политики файлового ввода-вывода. Я встречал много разных: stdin+stdout, a.in/txt, input.txt, $problem.in/txt, где-то даже a.in+stdout. В последнее время, как мне показалось, хорошей практикой считается допускать стандартные потоки, и файловый ввод-вывод. Но предположим, что такого решения нет, и остановимся на первых двух: только файлы или только стандартный ввод-вывод. Что из этого, по-вашему, должна поддерживать тестирующая система?

Я приведу свои аргументы в пользу stdin/stdout.

  1. Человек может написать минимальный работающий кусок кода, начав с пустого листа, проверить его работу из консоли и заслать в систему, потратив минимум времени. Есть разные кейсы. Топкодер, где код пишется в арене/на вебсайте; туда же сейчас присоденяются HackerRank, CSAcademy и т.д. Это и мой сегодняшний пример с засыланием предподсчёта, когда основное решение выводит инициализацию массива в отдельный файл, в который я потом в редакторе добавляю условные int main() и cout << a[n] << endl. Это задача А на Codeforces, которую хочется сдать до того, как ты написал шаблон, если шаблона по какой-то причине нет заранее.

  2. В файлах есть много разных стилей наименования, в которых легко опечататься. Файлы можно забыть. Да, это проверка на внимательность. Конечно, участник высокого уровня должен уметь и файлы замечать, и приписки в Input Format'е о том, что количество запросов второго типа не превосходит 5, и многое другое подобное. Но сейчас считается хорошим тоном от этого уходить в сторону собственно решения задач. Десять лет назад контест от Андрея Лопатина, где в файлах можно было встретить подколки вида "kitten.in / k1tten.out", был уместен и свеж. Десять лет назад на финалах просили вывести число ровно с тремя знаками после запятой, ни больше ни меньше, и без ошибок перебить с печатных условий английскую фразу в десяток слов. Сейчас не десять лет назад.

  3. Ответ на контраргумент "input.txt/output.txt строго удобнее": не надо за участника решать, как участнику удобнее тестировать локально! Я использую схему "a.in + stdout", например. Каждая задача в отдельной папке, название файла соответствует названию бинарника с задачей, вывод всегда консольный, потому что файловый бывает нужен крайне редко, а консольный удобнее смотреть. Открытие файла завёрнуто в ifdef, потому что я его сделал под свои нужды для удобства тестирования, и раз уж я хочу написать непортабельный код, то я сам должен позаботиться о том, чтобы он нигде больше не компилировался. Почему вместо этого я должен заворачивать в ifdef строки, нужные для взаимодействия с системой?

Да начнётся холивар!

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

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

Отвечаю dalex-у.

А в чем плюсы stdin / stdout то? Вроде input.txt / output.txt строго удобнее.

Во-первых, в файлы можно несколько тестов засовывать, и запускать либо верхний, а остальные тесты тогда сохранены ниже, либо как мультитест, что вообще здорово.

С input согласен. Где здесь профит от output?

Во-вторых, их не надо переименовывать от задачи к задаче, как  * .in /  * .out.

Это я вообще считаю строго неправильным. Одна задача -- один инпут. На тех же кодфорсах, например, довольно удобно иметь свои старые тесты по задаче, если тебя взломали и ты тестируешь решение заново. Не говоря уж о командных контестах, в которых может вестить работа над несколькими задачами одновременно. А если для задачи выделять папку, то появляется бонус в том, что там может жить генератор, скрипт для стресс-теста и подобное, не засоряя остальной контест.

Ну и если уж так нравится stdin, напиши #ifdef на нулевой минуте просто.

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

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

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

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

    Плюсую. Без консоли особенно неудобно (невозможно) тестировать интерактивные задачи

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

    После этого коммента даже задумался, как бы я организовал IO без консоли. Все придуманные варианты кажутся убогими...

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

Нормально. Сейчас свое видение ситуации опишу.

Вообще, вариантов не два, а три: 1) stdin / stdout, 2) input.txt / output.txt, 3) problemName.in / problemName.out.

Сразу пройдусь по третьему варианту. Это худшее, что можно было придумать. Я думаю, что огромное число команд ловили свои RE1 / WA1 из-за того, что забыли поменять файлы. Если кто не понял, происходит это так. Начинается контест, видишь задачу A, там файлы (условно) a.in / a.out. Решил, сдал. Видишь задачу B. Копируешь проект / исходник / класс с задачи A / шаблона или стираешь в ней код и пишешь задачу B. Дальше два варианта.

  1. Шаблона нет, и ты вынужден, матерясь, создавать файл b.in руками. Создаешь, теряешь полминуты, сдаешь задачу. И потом еще раз 5-10, в зависимости от сложности контеста, делаешь то же самое. Это в конце концов задалбывает, и ты понимаешь, что с шаблоном-то удобнее.
  2. Шаблон есть, и локально все тестируется на input.txt / stdout или input.txt / output.txt. Все работает, сабмитишь, получаешь RE 1. В очередной раз проклинаешь авторов контеста.

В обоих ситуациях надо потратить время на бесполезное занятие — поменять #define TASK "problemName", а в первом случае еще и создать файлы. В случае stdin / stdout или input.txt / output.txt этого делать не надо — все задачи имеют одинаковый формат.

Аргумент о том, что хорошо держать тесты в разных файлах сомнителен. Во-первых, не так уж часто это и надо. Во-вторых, тесты можно скопировать в Notepad++ / любой другой редактор. В третьих, если уж реально надо, локально ты сам себе хозяин, как удобно, так и делай. Пропиши в шаблоне разные файлы для разных задач и будь счастлив. А остальным не надо навязывать. Никто не проиграет от того, что ввод-вывод во всех задачах одинаковый. Но если он разный, всегда случается то, что я выше описал.

Теперь первый и второй варианты. Они тупо эквивалентны, потому что на нулевой минуте в шаблоне в любом случае задается формат ввода-вывода. Если в тестирующей системе используются потоки — не пишешь ничего в #else (комментишь input.txt / output.txt в #else), если используются input.txt / output.txt — пишешь (раскомменчиваешь) их в #else. И все. Маленький плюс (совсем маленький) input.txt / output.txt — они предпочтительнее для тех, кто сам использует input.txt / output.txt, но лишь потому, что такие участники просто экономят несколько секунд на написание #ifdef.

И я еще вот чего не понимаю. ifsmirnov бомбит с того, что не дописал input.txt / output.txt и сабмитнул без них. И при этом он признается, что у него в коде написан #ifdef. Так почему бы их не указать в #else в шаблоне на старте контеста? Это намного эффективнее, чем ручками дописывать input.txt / output.txt перед каждым сабмитом.

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

    У меня немного другой вокрфлоу. У меня есть шаблон, в котором в #ifdef стоит ввод из {a..z}.in для локального использования. Для создания контеста есть скрипт, который создаёт папки a,b,.., в каждой из которых есть файл $i.cpp и $i.in, причём в $i.cpp через sed уже подставлено правильное имя файла в #ifdef-е. Кроме того, для разных типов файлов в системе этот скрипт подставляет разные вещи в #else: это либо ничего, либо input.txt/output.txt, либо assert(freopen("name.in", ...)) -- причём последнее подставляется в том числе в локальную часть #ifdef-а, чтобы ты точно не забыл указать правильные файлы. (На самом деле у меня конкретно скрипт чуть послабее, такой мы используем на ноуте сокомандника.) И пока всё идёт нормально, проблем нет, ты тратишь примерно ноль лишнего времени.

    А потом начинается веселье. Ситуация 1: ты понял, что решение задачи -- полная фигня, и решил написать заново с чистого листа, при этом на всякий случай оставив старый код. Ты копируешь чистый шаблон (не настроенный под данный контест!)... и хорошо, если вспомнишь про файлы. Ситуация 2 (которая как раз у меня и была, она не подходит под шаблон): тебе по какой-то причине нужно заслать отдельный почти чистый файл. Прекальк, например. Или решение на другом языке, питоне или джаве. Тут уже никакие шаблоны не помогут.

    P.S. Что у тебя за редактор такой, в котором создание файла b.in полминуты занимает? :) Всем рекомендую: :vs b.in<Enter> -- пять секунд и никаких проблем! (речь про vim, естественно)

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

Плюсую за stdin / stdout:

Мне кажется это наиболее гибкий вариант, хочешь пиши себе ./a < a.in > a.out, хочешь ./a < a.in, хочешь просто ./a.

Правда, если кто-то привык к какой-нибудь IDE по типу MSVC или Eclipse, то возможно этот вариант не всегда удобен. Я когда использовал MSVC, то использовал вариант input.txt/stdout локально, а тесты к задаче сохранял в конце файла с решением в закомменченной секции. И вариант с problemName.{in, out} мне пришелся бы не по душе по примерно тем же причинам, что dalex перечислил выше.

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

Мне казалось, любой адекватный участник на C++ просто пишет #ifdef на пару freopen в начале тура (скорее даже перед туром), и забывает о файловом вводе-выводе до конца контеста. Что касается Java — сторонники этого языка вынуждены страдать и писать много лишнего кода, так язык устроен вроде.

Честно говоря, не вижу смысла дальше обсуждать тот факт, что наша система не такая, как остальные. По-моему input.txt/output.txt — это наименьшая из её проблем =(

P.S. Хотя на правах холивара, почему бы и нет =)

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

    Так себе можно забыть, если файлы зависят от задачи. (К фиксированным input/output, это конечно не относится)

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

Я пользуюсь stdin/stdout локально, потому как удобнее дебажить. К тому же, аргумент "удобнее тестировать, запихнул в файлы тесты и тестишь(пардон за тавтологию)" под Windows 10 не работает — возможность вставки из буфера обмена в консоль сделала своё грязное дело.

Но если думать о людях, которые только начинают своё развитие в направлении ОП, то, наверное, лучше stdin/stdout — ведь чем больше на старте непонятного, тем меньше желания заниматься. Кто-то скажет (наверняка какой-то корифей), что это всего 2 строчки кода, что их просто надо заучить и т.д., но ведь программирование — не та дисциплина, где заучивание работает, верно...?

P.S. всё-таки двойной ввод-вывод лучше, о вкусах ведь не спорят :)

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

    под Windows 10

    А при чем тут Windows 10, так всегда же можно было?

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

      Неудобно раньше было, Alt + Space + E + P, и при этом стоит сесть за русскую винду — хоткей ломается, приходится мышкой елозить-копипастить тест, что жутко раздражает и замедляет. Да и, помнится, с копированием что-то не то было, уже не помню что именно.

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

        Вроде как с копированием разных кодировок проблемы были, в задачах на строки проблемы были с правильным распознаванием символов

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

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

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

    Шел 2016 год, в windows наконец появилась возможность вставки из буфера обмена в консоль..

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

Вот забавное решение случая problemName.in / problemName.out для java:

public class D {
    public static void main(String[] args) {   
      String task = D.class.getName().toLowerCase();      
      String input = task + ".in";
      String output = task + ".out";
      // PUT YOU CODE HERE
    }
}

Например, если вы решили задачу D и хотите написать B, то просто копируете в IDE D.java и вставляйте как B.java. Имена файлов поменяются автоматически, а рантайм ерор напомнит вам создать отсутствующие фалы.

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

Я предпочитаю использовать stdin/stdout. Поэтому, чтобы избежать проблем с засылкой кода на контестах, где просят problemName.in / problemName.out , я просто добавляю вот такой класс сразу после инклюдов:

#define FILENAME "filter"
struct FileOpener
{
	FileOpener()
	{
		FILE* f = fopen(FILENAME".in", "r");
		if (f)
		{
			fclose(f);
			freopen(FILENAME".in", "r", stdin);
			freopen(FILENAME".out", "w", stdout);
		}
	}
} fileopener;

Это сразу избавляет от необходимости добавлять какие-нибудь дефайны, а также оставлять первые две строки в мейне. Разве что в этом случае тоже есть проблема, можно случайно забыть создать представителя класса, но эта проблема может возникнуть разве лишь в одной задаче :-)