Как пишутся сложные мульти-кайны?

Я определяю мультикуайн как:

Набор из n программ на n различных языках программирования, так что каждая из них, когда не вводится, выводит свой точный исходный код, а при вводе n в качестве ввода выводит исходный код *n* -ной программы.

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

Сложный в данном случае означает "для значений n, больших или равных 2". Я считаю, что решение для n = 2 достаточно сложен в этом случае. Тем не менее, общее решение (читай: стратегия) для всех значений n является целью.

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

1 ответ

Решение

Нет ничего необычного в квинах, многоязычных квинах, мультикуинах, вы это называете. Все они могут быть написаны в значительной степени автоматически.

Вот, в качестве примера, болотный стандарт, подробный, не элегантный, неэффективный quine в C++. Однако, со всеми его недостатками, довольно легко изменить, чтобы сделать то, что мы хотим.

#include <iostream>
#include <string>
#include <cstdlib>

std::string show (const std::string& in) {
    std::string res = "\"";
    for (std::string::const_iterator it = in.begin(); it < in.end(); ++it) {
        switch (*it) {
            case '"':
            case '\\':
                res += '\\';
            default:
                res += *it;
        }
    }
    res += "\"";
    return res;
}

int main (int argc, char* argv[])
{
    std::string arr[] = { // beginning ends here
"#include <iostream>",
"#include <string>",
"#include <cstdlib>",
"",
"std::string show (const std::string& in) {",
"    std::string res = \"\\\"\";",
"    for (std::string::const_iterator it = in.begin(); it < in.end(); ++it) {",
"        switch (*it) {",
"            case '\"':",
"            case '\\\\':",
"                res += '\\\\';",
"            default:",
"                res += *it;",
"        }",
"    }",
"    res += \"\\\"\";",
"    return res;",
"}",
"",
"int main (int argc, char* argv[])",
"{",
"    std::string arr[] = { // beginning ends here",
"======",
"    };",
"    int n = argc == 1 ? 0 : std::atoi(argv[1]);",
"    if (n == 0) {",
"        int i, j;",
"        for (i = 0; arr[i] != \"======\"; ++i) std::cout << arr[i] << std::endl;",
"        for (j = 0; j < sizeof(arr)/sizeof(arr[0]); ++j) std::cout << show(arr[j]) << ',' << std::endl;",
"        for (++i; i < sizeof(arr)/sizeof(arr[0]); ++i) std::cout << arr[i] << std::endl;",
"    } else {",
"    }",
"}",
    };
    int n = argc == 1 ? 0 : std::atoi(argv[1]);
    if (n == 0) {
        int i, j;
        for (i = 0; arr[i] != "======"; ++i) std::cout << arr[i] << std::endl;
        for (j = 0; j < sizeof(arr)/sizeof(arr[0]); ++j) std::cout << show(arr[j]) << ',' << std::endl;
        for (++i; i < sizeof(arr)/sizeof(arr[0]); ++i) std::cout << arr[i] << std::endl;
    } else {
    }
}

Как видите, в основе программы лежит маленькая функция show, который принимает строку и возвращает ее представление в виде литерала C++. Общая структура выглядит следующим образом: выведите начальную часть массива строк; распечатать весь массив по каналу show; напечатать конечную часть массива. Массив строк представляет собой копию программы, вставленную в середину программы. Начальная часть отделяется от последней специальной "=====" строка, которая не скопирована из программы (печатается только один раз, через show).

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

Теперь это абсолютно тривиально, чтобы перевести это на любой язык программирования (например, FORTRAN). Предположим, что мы сделали это, и оно составлено из линий L1, L2, ..., LN. Мы вставляем эти заявления в заполнитель:

std::cout << "L1" << std::endl;
std::cout << "L2" << std::endl;
...
std::cout << "LN" << std::endl;

Мы модифицируем массив строк соответственно. Вуаля, у нас есть квайн, который может печатать сам, а также квин в FORTRAN, в зависимости от аргумента командной строки.

Хорошо, а как насчет Фортрана Куайна? Он может печатать только сам, а не формула C++. Нет проблем, давайте скопируем квинну C++ обратно в квинту FORTRAN.

Но квин C++ уже содержит всю квинту FORTRAN, дважды?

Нет проблем, так как квитанция FORTRAN уже может печатать сама. Таким образом, нам нужно только скопировать исходные строки C++ обратно в FORTRAN. Не нужно дублировать Фортран внутри себя еще раз (или дважды).

Нам нужно лишь немного изменить Фортран. Когда мы просим квинту FORTRAN напечатать квинну C++, она должна напечатать все строки C++, а также все строки FORTRAN, дважды: один раз как Li и как только std::cout << "Li" << std::endl;точно так же, как в C++ quine. Затем мы возвращаем квайн C++ (включая квинту FORTRAN) обратно.

Нам также нужно перенести эти модификации FORTRAN обратно в C++ (то есть изменить std::cout << "Li" << std::endl; линии). И волна модификаций останавливается здесь.

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

Я призываю вас на самом деле сделать все это.

Другие вопросы по тегам