到底llhuii如何在42个字节的Python中输出Evil Number?


71

这是Python中打高尔夫的技巧问题,涉及无政府状态高尔夫上的邪恶数字问题。

如果数字的二进制扩展数具有偶数1,则该数字是有害的。面临的挑战是打印前400个邪恶数字0,3,5,...,795,797,798,每行打印一个。

Python 2提交由llhuii领导,提供42字节的解决方案。次佳的是mitch的46个字节,然后是五个47字节的提交。llhuii似乎发现了一些真正神奇的东西,在过去两年多的时间里,许多强大的Python高尔夫球手都无法使用它。对于如此短的高尔夫球,节省4或5个字节是巨大的。

Python 2分数表

我仍然有47个字节。我希望我们可以解决这个社区难题。如果我们共同获得答案,我将以所有贡献者的名义提交。这个问题的答案可以是一段代码,一个新想法或一段分析。如果您是llhuii,请不要为我们宠坏它。

尽管由于此问题是“无尽的”,所以未公开提交,但我们得到了一些线索。获胜的提交花了0.1699秒来运行,比任何其他时间都要长得多,这表明该方法效率低下。根据字节统计,在42个字符中,23个字母数字字符[0-9A-Za-z]和19个ASCII符号。这意味着llhuii解决方案中没有空格。

您可以在问题页面上测试代码,从语言下拉列表中选择Python或上传.py文件。注意:

  • 使用Python 2.7
  • 您的代码必须是可打印的完整程序
  • 没有输入用于解决此问题,例如
  • 您的程序只需打印给定的400个值,即使较大的值会中断
  • 程序有2秒的运行时间
  • 程序可能会因错误而终止
  • 您可以使用exec;“ exec被拒绝”是指shell exec

2
还可能值得注意的是,该序列是“ Thue-Morse序列A010060中的零的索引”。(来源:oeis
Conor O'Brien

Answers:


51

这与llhuii的解决方案不同,但它的长度也为42个字节。

n=0;exec'print n;n^=(n^n+2)%3/2;n+=2;'*400

在线尝试!

感谢@JonathanFrech,我们现在有40个字节。

n=0;exec'print n;n=n+2^(n^n+2)/2%3;'*400

在线尝试!

还有一个字节要保存,总共39个。

n=0;exec'print n;n=n+2^-(n^n+2)%3;'*400

在线尝试!


1
出于好奇,您如何知道42字节的版本与llhuii的版本不同?(我从未参加过无政府状态高尔夫)
路易斯·门多

6
@LuisMendo“ 统计信息”选项卡列出了23个字母数字字节和19个ASCII符号,因此没有空格。除非llhuii写下print+n,否则它们的解决方案必须不同于我的解决方案。
丹尼斯,

嗯,所以即使您不知道代码,也可以获得一些信息。真好。谢谢!
路易斯·门多

您认为38岁的机会吗?从理论上讲,-通过移动print~nprint-n并使用&或可以潜在地去除符号~,尽管我没有任何工作可用。此外,n=0;exec"print n;d=n^n+2;n^=d^-d%3;"*400是漂亮,但40字节。
xnor

print-n似乎不太可能,因为n和的设置位之间没有简单的关系-nprint~n从理论上讲,这听起来更有希望,但是用这种方法我不能超过40个字节。
丹尼斯

28

获取39个字节

这说明了我如何获得39字节的解决方案,Dennis和JonathanFrech也分别找到了该解决方案。或者,相反,它以一种比我实际的方法更好的方式解释了事后看来如何得出答案,因为我的实际方法充满了混乱的推理和死胡同。

n=0
exec"print n;n=n+2^-(n+2^n)%3;"*400

少写些高尔夫球,增加更多paren,这看起来像:

n=0
for _ in range(400):
  print n
  n=(n+2)^(-((n+2)^n))%3

位奇偶校验

我们从我的47字节解决方案中得出一个想法开始,输出所有形式的所有数字,n=2*k+b其中的数字是k递增计数的,0,1,...,399并且b是一个奇偶校验位,该总数使1的总数为偶数。

让我们写par(x)位奇偶校验x,那就是XOR( ^)都在位x。如果偶数个1位(数字是邪恶的),则为0;如果奇数个1位,则为1。对于n=2*k+b,我们有par(n) = par(k)^b,因此要实现邪恶,par(n)==0我们需要b=par(k),即的最后一位是n前几位的位奇偶校验。

我在打高尔夫球第一的努力都在表达par(k)首先直接bin(k).count('1')%2,然后用位操作

奇偶校验更新

不过,似乎没有一个简短的表达。相反,它有助于意识到还有更多信息可以使用。不仅仅是计算当前数字的位奇偶校验,

k  ---->  par(k)

当我们k增加到时,我们可以更新位奇偶校验k+1

k   ---->  par(k)
      |
      v
k+1 ---->  par(k+1)

也就是说,由于我们要进行递增计数k=0,1,2,...,因此我们仅需要维护当前的位奇偶校验,而不必每次都从头开始进行计算。位奇偶校验更新par(k+1)^par(k)是从k到翻转的位数的奇偶校验k+1,即par((k+1)^k)

par(k+1) ^ par(k) = par((k+1)^k)
par(k+1) = par(k) ^ par((k+1)^k)

形式 (k+1)^k

现在我们需要计算par((k+1)^k)。似乎我们一无所获,因为计算位奇偶校验正是我们要解决的问题。但是,数字表示为(k+1)^k形式1,3,7,15,..,即小于2的幂的形式,这是位黑客经常使用的事实。让我们看看为什么。

当我们增加时k,二进制进位的作用是将最后一个0和所有的都反1转到它的右边,0如果没有,则创建一个新的前导。例如拿k=43=0b101011

      **
  101011  (43)
 +     1
  ------
= 101100  (44)

  101011  (43)
 ^101100  (44)
  ------
= 000111  (77)   

引起进位的列用标记*。它们1改变为a 0并传递一个进位位1,继续向左传播直到撞到一个0in 为止k,改变为1。左侧的任何位均不受影响。因此,当k^(k+1)检查哪个位的位置变为kk+1,它会找到最右边的位置01右边的。也就是说,更改的位形成后缀,因此结果为0,后跟一个或多个1。如果没有前导零,则存在二进制数1, 11, 111, 1111, ...,该二进制数比2的幂小。

电脑运算 par((k+1)^k)

现在我们了解到(k+1)^k仅限于1,3,7,15,...,让我们找到一种计算此类数字的位奇偶校验的方法。在这里,一个有用的事实是和之间的1,2,4,8,16,...交替模,因为。因此,取模gives 恰好是它们的位奇偶校验。完善!3122==-1 mod 31,3,7,15,31,63...31,0,1,0,1,0...

因此,我们可以做的更新par(k+1) = par(k) ^ par((k+1)^k)

par(k+1) = par(k) ^ ((k+1)^k)%3

使用b作为存储奇偶校验的变量,这看起来像

b^=((k+1)^k)%3

编写代码

在代码放在一起,我们开始k和校验位b都在0,然后反复打印n=2*k+b和更新b=b^((k+1)^k)%3以及k=k+1

46个字节

k=b=0
exec"print 2*k+b;b^=(k+1^k)%3;k+=1;"*400

在线尝试!

我们删除了周围k+1((k+1)^k)%3原因,因为Python优先级还是会先进行添加,看起来很奇怪。

代码改进

通过直接使用单个变量n=2*k+b并直接对其进行更新,我们可以做得更好。这样做k+=1对应n+=2。并且,更新b^=(k+1^k)%3对应于n^=(k+1^k)%3。在这里,k=n/2在更新之前n

44个字节

n=0
exec"print n;n^=(n/2+1^n/2)%3;n+=2;"*400

在线尝试!

我们可以通过重写来缩短n/2+1^n/2(请记住这是(n/2+1)^n/2

n/2+1 ^ n/2
(n+2)/2 ^ n/2
(n+2 ^ n)/2    

由于/2删除了最后一位,所以我们在异或之前还是之后都没有关系。因此,我们有n^=(n+2^n)/2%3。我们可以注意到,模拯救另一个字节3/2相当于*2相当于-,并指出n+2^n是即使如此划分是不实际的地板减半。这给n^=-(n+2^n)%3

41个字节

n=0
exec"print n;n^=-(n+2^n)%3;n+=2;"*400

在线尝试!

最后,我们可以将操作n^=c;n+=2合并到中n=(n+2)^cc有点。之所以有效,是因为^c仅作用于最后一位,+2而不关心最后一位,因此操作上下班。同样,优先级让我们省略parens和write n=n+2^c

39个字节

n=0
exec"print n;n=n+2^-(n+2^n)%3;"*400

在线尝试!


13

这给出了我(xnor)的47字节解决方案,并为我带来了思路。如果您想自己弄清楚,请不要阅读。

一个自然的第一个想法是遍历数字0到799,仅以二进制打印偶数为1的数字。

52个字节

for n in range(800):
 if~bin(n).count('1')%2:print n

在线尝试!

在此,~取位补码以便切换even<->odd计数并仅在偶数计数时给出真实值。

我们可以通过生成所有值而不是过滤来改进此方法。请注意,输出值为数字0到399,每个输出值后面都附加了一个位,以使1位的数量为偶数。

0 = 2*0 + 0
3 = 2*1 + 1
5 = 2*2 + 1
6 = 2*3 + 0
...

因此,n日数要么是2*n+b用两种b=0b=1。比特b可以通过计数被发现1在的位的n和取数模2。

49个字节

for n in range(400):print 2*n+bin(n).count('1')%2

在线尝试!

我们可以2*通过遍历来减少2个字节0,2,4,...,这不会增加的计数1。我们可以使用exec运行400次并n每个循环增加2 的循环来做到这一点。

47个字节

n=0;exec"print n+bin(n).count('1')%2;n+=2;"*400

在线尝试!

而且,这就是我的47字节解决方案。我怀疑,即使不是全部,其他所有47字节的解决方案都差不多。


1
您的47个字节长exec吗?
乔纳森·弗雷希

1
@JonathanFrech是的,当页面上显示“ exec被拒绝”时,它不是指Python,exec而是命令行exec
xnor

9

llhuii的Python 3提交

这是撰写本文时针对Evil Numbers的Python 3提交:

在此处输入图片说明

llhuii可能将其技巧移植到了Python 3,并提出了一个解决方案

  • 比其Python 2解决方案长3个字节,并且
  • 具有45 −(25 + 18)= 2个字节的空白。

从字面上将xnor的47B移植到Python 3,我们得到以下50B:

n=0;exec("print(n+bin(n).count('1')%2);n+=2;"*400)

我以提交ppcg(xnor)。(它向exec和添加括号print,它们现在是函数。)它与其他Python 3答案具有不同的代码统计信息,所有这些答案中都包含一些空格。有趣!

不过,有一种更短的方法可以重写它(exec在Python 3中会失去竞争优势):

n=0
while n<800:print(n+bin(n).count('1')%2);n+=2

它是49个字节。我以提交ppcg(xnor,alternative)。就像llhui的答案一样,它有两个字节的空格!这使我相信llhuii的Python 3答案看起来像这样(换行符,然后是一个while循环。)因此llhuii可能像我们一样exec在Python 2和whilePython 3中使用。这解释了空白差异。


我们的47B在Python 3中变成了49B。现在,有趣的是llhuii的42B并未变成44B,而是变成了45B!关于llhuii的解决方案,在Python 3中需要多花一个字节。这可能意味着很多事情。

  • 首先想到的是除法:也许llhuii /在Python 2中使用了,后来//在Python 3中使用了。(如果像我们这样以两位数计数,那么n/2可能会用来向右移n一点?)

  • 想到的另一件事是打印后的一元运算符。我们print blah成为了print(blah)(额外1个字节),但是如果llhuii print~-blah在Python 2中编写了类似的内容,那么它将print(~-blah)在Python 3中成为。

  • 也许还有其他想法。请告诉我。

所有Py3解决方案的代码统计信息,包括现在的我的:

在此处输入图片说明


1
我发现有趣的是,他们的Python 3解决方案比他们的Python 2解决方案快得多。他们要么使用某些在Python 3中效率更高的Python功能,要么毕竟不是一个简单的端口(也许他们找到了比直接端口短的Python 3解决方案)。
乔纳森·弗雷希

2
anagol上的运行时有很大的差异,我在OP上评论说llhuii的运行时在这里使我认为它们的Py2运行时只是一个红鲱鱼/离群值
Lynn

另外,我认为同或找到了一个非常类似的技巧,并在其上(不能有改善多方面打印邪恶的数字,对不对?)和他们的解决方案是足够快!
林恩

7

其他方法

1)使用A001969的公式

除了转换为二进制,还可以利用以下公式(来自OEIS):

a(1) = 0
for n > 1: a(n) = 3*n-3-a(n/2) if n is even
           a(n) = a((n+1)/2)+n-1 if n is odd

我非常不喜欢使用Python打高尔夫球,所以我什至不尝试。但是这是JS中的快速尝试。

注意:我认为这不是有效的JS提交,因为它只是填充数组而不显示它。即使这样,它也比当前最好的JS解决方案(45个字节)长5个字节。但这不是重点。

for(a=[n=0,3];n<199;)a.push(2*++n+a[n],6*n+3-a[n])

希望它可以给您一些启发。

使用数组可能不是一个好主意,因为需要对其进行初始化和更新。相反,使用递归函数可能会更有效(在代码大小方面),这将解释为什么胜出的解决方案比其他解决方案花费更多时间的原因。

2)用替换构建Thue-Morse序列

从理论上讲,此代码应工作:

n=0;a="1";b="0";exec"t=a;a+=b;b+=t;print(int(b[n]))+n;n+=2;"*400

在线尝试!(可运行版本限制为20个条款)

它计算具有连续替换的Thue-Morse序列,并在同一循环中查找1(邪恶数字)的位置。

但:

  • 目前的格式太长了
  • 快速导致内存溢出

3)用按位运算建立Thue-Morse序列

从Wikipedia的Thue-Morse序列直接定义开始,我来到了这种算法(切换回JS ...对不起):

for(e=n=0;n<799;)(e^=!(((x=n++^n)^x/2)&170))||console.log(n)

在这里,我们跟踪e中序列的当前危害,并使用170作为字节中奇数位的位掩码。


我喜欢递归函数的想法,但是Python在样板方面非常糟糕:f=lambda n:_ for n in range(400):print f(n)已经占用了43个字节。也许有一种方法可以通过构建引用自身的数组或在末尾添加将来元素的数组来模拟递归。
xnor

2
此外,llhuii的解决方案中有没有空格,所以他并没有使用defforwhilelambda(用参数至少),等等
斯蒂芬

@Stephen类似的东西while~0:print~1不需要任何空格。
乔纳森·弗雷希

在方法3中,((x=n++^n)^x/2)仅找到最低的设置位显得有些冗长。那整个烂摊子可以用代替++n&-n在线尝试!
primo

@primo我不知道我在这里想什么以及如何得出这个麻烦的公式。¯\ _(ツ)_ /¯
阿尔诺

5

嵌套计数器方法

我有一个不同方法的想法,但是我对python高尔夫没有足够的经验,所以我将其留在这里供大家考虑,作为高尔夫的另一个可能起点。

空洞的想法:

n=0
i=1
for _ in"01":
 i^=1
 for _ in"01":
  i^=1
  for _ in"01":
   i^=1
   for _ in"01":
    i^=1
    for _ in"01":
     i^=1
     for _ in"01":
      i^=1
      for _ in"01":
       i^=1
       for _ in"01":
        i^=1
        for _ in"01":
          i^=1
          if n<800:print i+n
          n+=2

在线尝试!

九个级别的嵌套深度,所有循环都是相同的,因此在我看来,它们应该由构建exec"something"*9+"deepest stuff"。在实践中,我不知道是否可以使用for循环执行类似的操作。

打高尔夫球要考虑的事项:

  • 也许除了for循环以外,还有其他可能循环两次(我尝试了一种类似于quine的方法,将要执行的字符串作为格式化参数两次传递给自身,但是我的头突然爆炸了)。

  • 可能还有更好的替代方法if n<800:,这是这里需要的,因为否则我们将继续打印邪恶的数字,直到2 ^ 10



也许尝试嵌套列表推导而不是嵌套for循环?
Sparr

@Sparr然后问题是实际打印数字。在Python 2中,print它是语句,而不是函数,因此不能出现在理解中。
乔纳森·弗雷希

可能print '\n'.join([[[[[[[[[foo]foo]foo]foo]foo]foo]foo]foo]foo])
Sparr

@Sparr然后问题就在于使列表变平;str.join仅适用于包含字符串的列表,并且不得打印多余的列表字符。单独进行格式化将占用大量字节。
乔纳森·弗雷希

5

想法:更短的位奇偶校验

bin(n).count('1')%2计算位计数的奇偶校验需要花费很多字符。也许算术方法更短,尤其是在给定的位长度有限的情况下。

一个可爱的等长方式是int(bin(n)[2:],3)%2,将二进制值解释为基数3(或任何奇数基数)。不幸的是,有4个字节用于删除0b前缀。它也可以做到int(bin(n)[2:])%9%2

另一个想法来自使用xor组合位。如果n具有二进制表示形式abcdefghi,则

n/16 = abcde
n%16 =  fghi

r = n/16 ^ n%16 has binary representation (a)(b^f)(c^g)(d^h)(e^i)

因此,r=n/16^n%16只有当n邪恶时,邪恶才是邪恶的。然后,我们可以重申,作为s=r/4^r%4一个值s0,1,2,3,其中12没有邪恶,可检查有0<s<3

52个字节

n=0;exec"r=n/16^n%16;print(0<r/4^r%4<3)+n;n+=2;"*400

在线尝试!

结果证明时间更长。有很多旋钮可以切换如何拆分数字,如何检查最终数字(可能是基于位的查找表)。我怀疑这些只能走这么远。


使用to_bytes整数函数是否有可能?我对此表示怀疑,但需要考虑一下:)
HyperNeutrino

@HyperNeutrino我认为只有Python 3吗?
xnor

是的,我不好:/ rip
HyperNeutrino

9
只需使用0bint(bin(n),13)%2!:D
Noodle17年

3
进展!Noodle9的把戏提供了一个44字节的解决方案:n=0;exec"print~int(bin(n),13)%2+n;n+=2;"*400
Lynn

4

从构造上讲,n+n^n它总是邪恶的,但是我可怜的Python技能只能提出61字节的解决方案:

for n in sorted(map(lambda n:n+n^n,range(512)))[:400]:print n

感谢@Peilonrayz保存5个字节,而@ Mr.Xcoder保存1个字节:

for n in sorted(n^n*2for n in range(512))[:400]:print n

55个字节for n in sorted(n^n*2for n in range(512))[:400]:print nn+n^nn^n*2
Xcoder先生17年

3

想法:A006068(“ a(n)被格雷编码为n”)

尼尔对所有2n XOR n事物进行排序的想法引起了我的兴趣,因此我试图找到这种类型背后的索引。我编写了这段代码,它表明我们可以编写如下代码

for n in range(400):x=a(n);print 2*x^x

a(n)A006068(n)在哪里。在线尝试!

但是,这假设我们有一些计算A006068的捷径。假设我们可以用4个字节(a(n)部分)进行计算,这已经是38个字节。真正的实现(在TIO标头中)要长得多。我认为对此没有太大希望。


3

想法:减少异或

如果您对所有位进行异或运算n,那将是0邪恶的和1非邪恶的。您可以使用递归函数(这样可能会花费更多时间?)来执行此操作,如下所示:

f=lambda n:f(n/2^n&1)if n>1else-~-n

这将为邪恶返回1。

这是35个字节,并检查数字是否为邪恶。不幸的是,filter已经是6个字节了,所以这并不是逐字的最佳解决方案,但是这个想法可能可以采用。


我认为您可以f=lambda n:n>1and f(n/2^n&1)or-~-n为-1字节做。
暴民埃里克(Erik the Outgolfer)'17

我尝试了@EriktheOutgolfer,但是f(n/2^n&1)返回0 时会导致错误...
HyperNeutrino

2

替换方法:{1-> {1,-1},-1-> {-1,1}}

您还可以将该替换进行10次{1-> {1,-1},-1-> {-1,1}},然后展平并检查1的位置

这是数学代码

(F = Flatten)@
Position[F@Nest[#/.{1->{1,-1},-1->{-1,1}}&,1,10],1][[;; 400]] - 1

您将如何在python中执行此操作?
Aneesh Durg

2
@AneeshDurg您是否在此解决方案中发现任何有趣的东西?开箱即用的想法,您可能会找到通往生活意义的方法AKA 42
J42161217
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.