Как пишутся сложные мульти-кайны?
Я определяю мультикуайн как:
Набор из 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;
линии). И волна модификаций останавливается здесь.
Вот и все, у нас есть две программы, которые могут печатать либо себя, либо друг друга, в зависимости от аргумента командной строки.
Я призываю вас на самом деле сделать все это.