编写藜的技巧


30

是产生输出这等同于程序的源代码的程序。在此网站上,我们通常只关心适当的奎因(在撰写本文时,当前定义是“输出的某些部分由程序的不同部分编码”)。

您对编写适当的quines或具有quine类特性的程序有什么建议?像往常一样,每个提示都应使用不同的答案。

tips  quine 

Answers:


14

使用eval以减少需要复制代码

大多数奎因需要两个代码副本。一个要执行,一个作为数据。如果您正在编写比赛用的quine,这最终可能会使源代码的长度加倍,使其难以维护,并使得分恶化。

合并两个副本意味着需要将一条信息用于两个目的。尝试将代码视为数据通常是不可能的,并且通常被视为作弊。但是,可以通过使用通常称为的内置语言,以多种语言将数据视为代码eval。因此,您的Quine基本上是将Quine的主体存储在一个变量中(以便您可以多次引用它),然后对该变量求值。

这是一个如何工作的示例(该示例是用Python编写的,但在许多其他语言中也是如此):

d='print("d="+repr(d)+"\\neval(d)")'
eval(d)

2
@QPaysTaxes:我的目标是在胜利条件允许的情况下,使我的代码具有可读性和可维护性。不幸的是,这与通常不习惯该语言的人的ASCII线噪声(或者如果我使用Jelly的话,只是常规的线噪声)通常仍然无法区分。

14

利用字符串格式

创建卷轴的最简单方法之一是定义一个字符串,然后使用字符串格式将其放入自身。

s='s=%r;print s%%s';print s%s

因此,在此示例Python quine中,我们声明一个字符串,该字符串的第一部分等于该字符串之前的内容s=,然后允许使用格式设置插入该字符串%r,最后我们将字符串后面的内容打印并格式化。 。尾随换行符是因为print打印尾随换行符。

因此,在Python中,模板实际上就是这个:

<A>'<A>%r<B>'<B>

要使用更多代码扩展现有的quine:

<more A>s='<more A>s=%r;print s%%s<more B>';print s%s<more B>

9

字符串化函数

在几种语言中,函数对象(或等效结构)隐式存储其源代码,并在转换为字符串时将其返回。这允许紧凑的quines,而无需使用字符串eval。这种语言的一个显着示例是JavaScript:

function f(){console.log(f+"f()")}f()

此代码定义并调用一个函数f,该函数在被调用时将打印其自己的源代码,然后对其进行调用。程序中唯一需要复制的部分是函数调用f()。当然,函数主体可以包含任意“有效载荷”代码,这些代码在调用函数时也将执行。


相同技巧的更紧凑版本可用于高尔夫语言GolfScript中

{".~"}.~

CJam

{"_~"}_~

每个这些quines首先定义一个匿名代码块(用大括号括起来),其行为与JavaScript中的函数对象非常相似:可以执行该代码块,并且如果将其字符串化,则返回其源代码。然后,其余代码(.~在GolfScript或_~CJam中)将执行该块,同时将其副本保留在堆栈中。然后,该块内的代码将一个字符串压入堆栈,从而在该块外重复该代码。解释器退出后,它将自动进行字符串化并打印出堆栈中剩余的所有内容。与JavaScript示例一样,也可以使这些代码块携带和执行附加代码的任意有效负载,而不必进行复制。


9

使用嵌套的字符串定界符而不进行转义

通常,逃避步骤是写奎因最困难的部分之一。几乎每个奎奴亚藜都需要这样做。问题是您以某种方式存储数据,并且需要复制将数据存储在quine输出中的代码。该代码将包含转义的数据形式,因此程序将看到未转义的形式,因此您需要重新转义它。

处理转义步骤的最简单方法是,如果仅在存在或不存在字符串定界符的情况下,数据的转义和未转义形式有所不同。因此,转义是在字符串周围添加一对新的字符串定界符的简单问题。不幸的是,这显然只有在字符串分隔符本身可以在数据中表示而没有转义的情况下才有效。

Perl是这种技巧起作用的语言的一个很好的例子。尽管其通常的字符串定界符为"…"or '…',但不太常用的q(…)嵌套,因此可以编写这种形式的quine:

$_=q($_=q(0);s/\d/$_/;print);s/\d/$_/;print

这是一个代码+数据quine。s///是一个正则表达式字符串替换操作;我们将其0用作标记,并在正则表达式中将其与\d(“任意数字”)进行匹配,以避免多次使用标记(尽管作为另一种优化,我们实际上可以0再次使用,因为Perl s///仅用替换了第一个匹配项)默认)。请注意,这里不需要显式的转义步骤,因为q(…)分隔符可以直接包含在数据字符串中。


8

代码+数据桶

藜的最一般的结构看起来像下面的伪代码:

data =“ 整个程序的转义版本,
        用标记替换该字符串 “
程序= data.replace(
  该表达式的计算结果为标记,但未提及该标记,
  转义(数据))
打印程序;

此结构可用于在大多数语言中编写(相当幼稚的)奎因。但是,在大多数评分系统上,它往往得分很差,因为您必须两次编写整个程序。但是,大多数奎因结构可以被认为是对此的优化。

这有一些微妙之处。在某些语言中,执行此操作最困难的部分是编写转义代码。在许多语言中,很难在不提及标记名称的情况下产生标记。在某些深奥的语言中,您将必须发明自己的字符串文字。不过,这三个操作都不会引起太多麻烦。

例如,我们可以使用repr,并使用2个字符的序列x"字符串(可表示为"x\"",即不使用x"字符串本身的字符串表示形式中的序列)作为标记来编写转义字符串的Python quine :

d='d=x"\nprint(str.replace(d,"x\\"",repr(d)))'
print(str.replace(d,"x\"",repr(d)))

2
可能值得注意的是(可能在另一个答案中),在esolangs中将字符串插入标记的位置通常很昂贵,并且可能值得对代码进行结构化,以使字符串本身是第一或最后一件事(可能与末尾加一或两个可以硬编码的字符),以便您知道该去哪里。
Martin Ender

@MartinEnder:我同意值得一提,但这可能是另一个答案(而不是对此答案进行评论或编辑)。多数quine技巧都是对该常规结构的修改,因此我想首先将其作为技巧,因为许多人不知道从哪里开始编写quine。

标记的替代方法是使用两个字符串,我对Glass使用了这两个字符串。
与Orjan约翰森

4

利用包装源代码

在相当多的语言(主要是2D语言)中,源代码可以环绕。在某些情况下(例如,在Befunge-98中,如果您的程序是单行程序),经过程序结尾将带您回到程序的开头。这种非线性行为意味着您可以同时编写位于字符串文字内部和外部的代码。一个不匹配"的字符串(或任何字符串分隔符)将有效地为您提供一个字符串,其中包含程序的其余所有部分("本身除外)。

使用此技巧的一个问题是,您将从的角度获得字符串",而不是从程序开始时获得字符串(如您所愿)。因此,重新安排程序以使其"出现在开始或结尾处可能是最容易的。这通常意味着将您的程序分成多个部分,并利用您的语言所具有的任何有趣/不寻常的流控制命令(大多数使字符串文字环绕程序的语言都可以很好地选择这些语言)。

一个很好的例子是@Justin的Befunge-98 quine

<@,+1!',k9"

无与伦比的"在节目的最后包装整个程序在一个字符串,所以(从右到左,由于<在开始),我们所要做的就是输出的程序(9k),然后输出双引号('!1+,)和退出(@)。这样可以节省程序的两个副本(一个作为代码,一个作为数据);这两个副本是同一段代码,只是以不同的方式进行解释。


4

记住奎因的结构

我喜欢将藜麦视为三个部分,而不是2:

  • 第1部分:生成第2部分和第3部分的数据表示。
  • 第2部分:使用数据以算法方式打印回第1部分。
  • 第3部分:解码表示以打印第2部分和第3部分。

这可以使人们更容易考虑奎因。这是一个Python quine,每行对应一个部分:

s = "print(\"s = \" + repr(s))\nprint(s)"
print("s = " + repr(s))
print(s)

有时,您使用eval或相似的方法来消除重复,但是通常我发现这有助于编写简单的Quines。

让我们看一下两个不同的Underload quines。这是第一个:

(:aSS):aSS

第一部分是(:aSS),它生成数据表示。第二个是:aS,它打印(:aSS)。第三部分是S,它打印:aSS

这是第二根藜:

(:aS(:^)S):^

起初,这似乎不合适。但是,如果展开奎纳,则会得到:

(:aS(:^)S):aS(:^)S

现在(:aS(:^)S)是第1部分,:aS第2部分,(:^)S第3部分。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.