【问题标题】:Code Golf: Fractran代码高尔夫:Fractran
【发布时间】:2010-12-17 12:28:43
【问题描述】:

挑战

编写一个充当Fractran 解释器的程序。在任何语言中,按字符数计算最短的口译员获胜。您的程序必须有两个输入:要执行的 fractran 程序和输入整数 n。该程序可以是任何对您的程序方便的形式——例如,2 元组列表或平面列表。输出必须是单个整数,即执行结束时寄存器的值。

分形

Fractran 是John Conway 发明的一种微不足道的深奥语言。 fractran 程序由正分数列表和初始状态 n 组成。解释器维护一个程序计数器,最初指向列表中的第一个部分。 Fractran 程序按以下方式执行:

  1. 检查当前状态与当前程序计数器下的分数的乘积是否为整数。如果是,则将当前状态乘以当前分数并将程序计数器重置到列表的开头。
  2. 推进程序计数器。如果到达列表末尾,则停止,否则返回步骤 1。

有关 Fractran 工作原理和原理的详细信息,请参阅 the esolang entrythis entry,了解数学好/坏数学。

测试向量

程序: [(3, 2)]
输入: 72 (2332)
输出: 243 (35)

程序: [(3, 2)]
输入: 1296 (2434)
输出: 6561 (38)

程序: [(455, 33), (11, 13), (1, 11), (3, 7), (11, 2), (1, 3)]输入: 72 (2332)
输出: 15625 (5 6)

奖励测试向量:

您的提交不需要正确执行最后一个程序即可成为可接受的答案。但是如果它这样做了,那就太棒了!

程序: [(455, 33), (11, 13), (1, 11), (3, 7), (11, 2), (1, 3)]输入: 60466176(210310
输出: 788860905221011805411728565282786229673206435109023004770278930664062( 100)

提交和评分

程序严格按照字符长度排序 - 最短的最好。随意提交布局合理且文档化的代码以及“缩小”版本的代码,以便人们可以看到发生了什么。

不允许使用语言“J”。这是因为其中一个链接页面上的 J 中已经有一个众所周知的解决方案。如果您是 J 粉丝,对不起!

然而,作为额外的奖励,任何可以提供工作的 fractran 解释器in fractran 的人都将获得 500 声望点奖励。万一出现多个自托管解释器,分数最少的解释器将获得赏金。

获奖者

在提交包含 1779 个分数的自托管 fractran 解决方案后,官方获胜者是 Jesse Beder's solution。但是,实际上,即使执行 1+1,该解决方案也太慢了。

令人难以置信的是,这已被另一种 fractran 解决方案击败 - Amadaeus's solution 仅用 84 个分数!在我的参考 Python 解决方案上运行时,它能够在几秒钟内执行前两个测试用例。它对分数使用了一种新颖的编码方法,这也值得仔细研究。

荣誉提及:

  • Stephen Canon's solution,165 个字符的 x86 程序集(28 字节机器码)
  • Jordan's solution 包含 52 个 ruby​​ 字符 - 处理长整数
  • Useless's solution 包含 87 个 Python 字符,虽然不是最短的 Python 解决方案,但它是为数不多的非递归解决方案之一,因此可以轻松处理更难的程序。它的可读性也很强。

【问题讨论】:

  • 致不可避免的亲密旅:Stack Overflow 上还有 100 多个其他带有代码高尔夫标记的问题。这是一个合法的帖子,并导致有趣和有启发性的答案。而且,它是一个社区 wiki,所以没有人可以从中获得嫖娼的名声。
  • @R。佩特:去和我之前的其他 104 代码高尔夫争论,或者在元数据上提出,然后 - 社区共识普遍是有利的。 @mmyers:谢谢,我会更新问题。
  • @R.Pate:目前的共识似乎相当强烈(+20 票赞成“没关系,如果你不喜欢它,请忽略它”):meta.stackexchange.com/questions/20912/so-weekly-code-golf。当然,您(和其他任何关心的人)应该投票,如果没有涉及到,还应说明您的观点。
  • 我没有先发制人地发表评论 - 我只是在有人投票结束后发表评论。
  • Just my 2 cents: 这是一个很好的问题,因为我通常喜欢 Code Golf,更重要的是因为它向我介绍了一个非常有趣的东西(以前从未听说过这种“语言”,但它太棒了!)。

标签: code-golf esoteric-languages


【解决方案1】:

Haskell,102 个字符

import List
import Ratio
l&n=maybe n((&)l.numerator.(n%1*).(!!)l)$findIndex((==)1.denominator.(n%1*))l
$ ghci Prelude>:m 列表比例 Prelude List Ratio> let l&n=maybe n((&)l.numerator.(n%1*).(!!)l)$findIndex((==)1.denominator.(n%1*))l 前奏列表比例> [3%2]&108 243 前奏列表比例> [3%2]&1296 6561 前奏列表比例> [455%33,11%13,1%11,3%7,11%2,1%3]&108 15625

88 对输入/输出格式的限制放宽。

import List
import Ratio
l&n=maybe n((&)l.(*)n.(!!)l)$findIndex((==)1.denominator.(*)n)l
Prelude List Ratio> let l&n=maybe n((&)l.(*)n.(!!)l)$findIndex((==)1.denominator 前奏列表比例> [455%33,11%13,1%11,3%7,11%2,1%3]&108 15625 % 1

【讨论】:

    【解决方案2】:

    Groovy136 117 107 个字符。

    调用为 groovy fractal.groovy [输入状态] [程序向量作为数字列表]

    a=args.collect{it as int}
    int c=a[0]
    for(i=1;i<a.size;i+=2) if(c%a[i+1]==0){c=c/a[i+1]*a[i];i=-1}
    println c
    

    样本

    bash$ groovy fractal.groovy 108 455 33 11 13 1 11 3 7 11 2 1 3
    Output: 15625
    

    【讨论】:

    • 您能否添加一些有关如何接受输入和代码的详细信息?看起来它们是命令行参数?样本向量的输出也很有指导意义。
    【解决方案3】:

    Perl, 84 82 字符

    使用标准输入。

    @P=<>=~/\d+/g;$_=<>;
    ($a,$%)=@P[$i++,$i++],$_*$a%$%or$i=0,$_*=$a/$%while$i<@P;
    print
    

    需要 110 个字符才能通过奖金测试:

    use Math'BigInt blcm;@P=<>=~/\d+/g;$_=blcm<>;
    ($%,$=)=@P[$i++,$i++],$_*$%%$=or$i=0,($_*=$%)/=$=while$i<@P;print
    

    【讨论】:

      【解决方案4】:

      C, 159 153 151 131 111 110 个字符

      v[99],c,d;main(){for(;scanf("%d",v+c++););while(d++,v[++d])
      *v%v[d]?0:(*v=*v/v[d]*v[d-1],d=0);printf("%d",*v);}
      
      $ cc f.c $ 回声 108 3 2 。 | ./a.out;回声 243 $ 回声 1296 3 2 。 | ./a.out;回声 6561 $ 回声 108 455 33 11 13 1 11 3 7 11 2 1 3 。 | ./a.out;回声 15625

      【讨论】:

      • 有趣的语言选择。
      • 你为什么在高尔夫中使用long long?你会比普通的int 失去那么多精度吗?
      • @Goody:你的意思是“糟糕的选择”。赢的机会肯定不大。 @Chris:第三个示例的中间计算为 2299171875 > 2^31。我第一次写的时候并没有意识到,但是我可以重新安排程序来避免这种情况......
      • 也许你可以使用_int64_int128
      • 不:因为假设分数是简化形式,我只是切换了除法和乘法的顺序,前三个测试现在适合 int :)
      【解决方案5】:

      Haskell,142 个字符

      无需任何额外的库和完整的 I/O。

      t n f=case f of{(a,b):f'->if mod n b == 0then(\r->r:(t r f))$a*n`div`b else t n f';_->[]}
      main=readLn>>=(\f->readLn>>=(\n->print$last$t n f))
      

      【讨论】:

        【解决方案6】:

        x86_64 程序集,165 个字符(28 个字节的机器码)。

        状态在 %rdi 中传递,程序(指向以 null 结尾的分数数组的指针)在 %rsi 中。根据通常的 C 风格调用约定,结果以 %rax 的形式返回。使用非标准调用约定或 Intel 语法(这是 AT&T 语法)会丢失更多字符,但我很懒;其他人可以做到这一点。几乎肯定可以通过重新安排控制流来节省一两条指令,如果有人想这样做,请随意。

        中间计算(状态*分子)最高可达 128 位宽,但仅支持 64 位状态。

        _fractran:
        0:  mov     %rsi,   %rcx    // set aside pointer to beginning of list
        1:  mov    (%rcx),  %rax    // load numerator
            test    %rax,   %rax    // check for null-termination of array
            jz      9f              // if zero, exit
            mul     %rdi
            mov   8(%rcx),  %r8     // load denominator
            div     %r8
            test    %rdx,   %rdx    // check remainder of division
            cmovz   %rax,   %rdi    // if zero, keep result
            jz      0b              // and jump back to program start
            add     $16,    %rcx    // otherwise, advance to next instruction
            jmp     1b
        9:  mov     %rdi,   %rax    // copy result for return
            ret
        

        删除 cmets、无关空格和详细标签 _fractran 以获得最小化版本。

        【讨论】:

          【解决方案7】:

          Python, 110 103 95 87 个字符

          frc.py

          ​​>
          def f(n,c):
           d=c
           while len(d):
            if n%d[1]:d=d[2:]
            else:n=d[0]*n/d[1];d=c
           return n
          

          test.py

          ​​>

          (显示如何驾驶它)

          from frc import f
          def test():
           """
           >>> f(108, [3,2])
           243
           >>> f(1296, [3,2])
           6561
           >>> f(108, [455,33,11,13,1,11,3,7,11,2,1,3])
           15625
           >>> f(60466176, [455, 33,11, 13,1, 11,3, 7,11, 2,1, 3])
           7888609052210118054117285652827862296732064351090230047702789306640625L
           """
           pass
          import doctest
          doctest.testmod()
          

          【讨论】:

          • i=0n=(n*c[i])/c[i+1] 放在同一行,用分号隔开。它将保存三个字符。您也许还可以使用if n%c[i+1]:i+=2 和类似的技巧来消除所有讨厌的空白。
          【解决方案8】:

          Ruby,58 57 56 53 52 个字符

          这是我第一次参加代码高尔夫比赛,所以请温柔一点。

          def f(n,c)d,e=c.find{|i,j|n%j<1};d ?f(n*d/e,c):n end
          

          用法:

          irb> f 108, [[455, 33], [11, 13], [1,11], [3,7], [11,2], [1,3]]
          => 15625
          
          irb> f 60466176, [[455, 33], [11, 13], [1, 11], [3, 7], [11, 2], [1, 3]]
          => 7888609052210118054117285652827862296732064351090230047702789306640625
          

          漂亮版(252):

          def fractran(instruction, program)
            numerator, denominator = program.find do |numerator, denominator|
              instruction % denominator < 1
            end
          
            if numerator
              fractran(instruction * numerator / denominator, program)
            else
              instruction
            end
          end
          

          Ruby,53 52 使用 Rational

          gnibbler's solution 的启发,我能够使用 Rational 获得一个解决方案,该解决方案减少到 53 52 个字符。 比上面的(不太优雅的)解决方案还要长一个。

          def f(n,c)c.map{|i|return f(n*i,c)if i*n%1==0};n end
          

          用法:

          irb> require 'rational'
          irb> f 60466176, [Rational(455, 33), Rational(11, 13), Rational(1, 11), Rational(3, 7), Rational(11, 2), Rational(1, 3)]
          => Rational(7888609052210118054117285652827862296732064351090230047702789306640625, 1)
          

          to_i 调用更漂亮的输出会增加 5 个字符。)

          【讨论】:

          • @Andy Dent:我不得不追踪您的其他帖子之一,以便为此 +1。
          • 您可以使用map 而不是each,因此它与52 并列
          • def f(n,c)(j=c.find{|i|i*n%1==0})?f(n*j,c):n end - 48
          • def f(n,c)c.find{|i|($j=i*n)%1==0}?f($j,c):n end - 48
          【解决方案9】:

          Java,200 192 179 个字符

          我想每个人都知道 Java 不会有最短的实现,但我想看看它会如何比较。它解决了琐碎的例子,但没有解决额外的例子。

          这是最小化版本:

          class F{public static void main(String[]a){long p=new Long(a[0]);for(int i=1;i<a.length;){long n=p*new Long(a[i++]),d=new Long(a[i++]);if(n%d<1){p=n/d;i=1;}}System.out.print(p);}}
          

          java -cp 。 F 108 455 33 11 13 1 11 3 7 11 2 1 3

          15625
          

          java -cp 。 F 1296 3 2

          6561
          

          这是清理后的版本:

          public class Fractran {
          
              public static void main(String[] args) {
                  long product = new Long(args[0]);
          
                  for (int index = 1; index < args.length;) {
                      long numerator = product * new Long(args[index++]);
                      long denominator = new Long(args[index++]);
          
                      if (numerator % denominator < 1) {
                          product = numerator / denominator;
                          index = 1;
                      } // if
                  } // for
          
                  System.out.print(product);
              }
          }
          

          【讨论】:

          • 你可以通过切换到new Long而不是Long.decode来节省更多字符。
          • 感谢您的提示 - 已实施!利用代码技术保存通常不会使用的字符是一项挑战。
          【解决方案10】:

          Python, 83 82 81 72 70 个字符。

          将输入作为 fractions.Fraction 对象很方便。与 Ruby 解决方案中的想法相同。

          def f(n,c):d=[x for x in c if x*n%1==0];return d and f(n*d[0],c) or n
          
          # Test code:
          from fractions import Fraction as fr
          assert f(108, [fr(3, 2)]) == 243
          assert f(1296, [fr(3, 2)]) == 6561
          assert f(108, [fr(455, 33), fr(11, 13), fr(1, 11), fr(3, 7), fr(11, 2), fr(1, 3)]) == 15625
          assert f(60466176, [fr(455, 33), fr(11, 13), fr(1, 11), fr(3, 7), fr(11, 2), fr(1, 3)]) == 7888609052210118054117285652827862296732064351090230047702789306640625
          

          【讨论】:

          • or前面的空格可以省略,最后不用计算换行符,所以应该是68
          • 你可以直接在 x 上取 d[0]。这也允许使用 lambda 函数。并且使用生成器,您可以使列表理解在第一次命中时停止以提高效率。总共 53 个字符(也发布在 gnibblers 解决方案):g=lambda n,c:next((g(n*x,c)for x in c if x*n%1==0),n)
          【解决方案11】:

          Golfscript - 32

          {:^{1=1$\%!}?.1={~@\/*^f}{}如果}:f ; 108 [[3 2]] f p #243 ;第1296章 [[3 2]] # 6561 ; 108 [[455 33][11 13][1 11][3 7][11 2][1 3]] #15625 ; 60466176 [[455 33][11 13][1 11][3 7][11 2][1 3]] f p # 7888609052210118054117285652827862296732064351090230047702789306640625

          【讨论】:

          • Golfscript 看起来应该是作弊,但它并没有被排除在外,它甚至还获得了奖励向量——所以我将这个标记为获胜者。
          【解决方案12】:

          Lua:

          整洁的代码:

          a=arg;
          ip=2;
          reg=a[1];
          while a[ip] do
              curfrac = a[ip] / a[ip+1];
              if (curfrac * reg) % 1 == 0 then
                  ip=2;
                  reg = curfrac * reg
              else
                  ip=ip+2
              end
          end
          print(reg)
          

          98 个字符的紧凑代码(Scoregraphic 在我的其他答案中建议减少,gwell 建议更多):

          a=arg i=2 r=a[1]while a[i]do c=a[i]/a[i+1]v=c*r if v%1==0 then i=2 r=v else i=i+2 end end print(r)
          

          从命令行运行,首先提供基数,然后提供一系列以空格分隔的数字,如下所示:

          C:\Users\--------\Desktop>fractran.lua 108 3 2
          243
          C:\Users\--------\Desktop>fractran.lua 1296 3 2
          6561
          C:\Users\--------\Desktop>fractran.lua 108 455 33 11 13 1 11 3 7 11 2 1 3
          15625
          

          (手动输入其中的一些内容,因为从命令行中获取内容很痛苦,尽管这是返回的结果)

          可悲的是没有处理奖金向量:(

          【讨论】:

          • tonumber() 可以因为自动转换而被删除。右括号后的分号和空格可以去掉。
          • if v%1==0 then i=2 r=v else i=i+2 end 可以替换为if v%1==0 then i=0 r=v end i=i+2 以节省五个字符。
          • 这不起作用,因为它不会完全重置到列表的开头。
          • 很抱歉,您能解释一下这两个sn-ps 代码的效果如何不同吗?我可能忽略了一些东西,但话又说回来,我确实简单地测试了我的更改,它似乎有效。
          • 它总是递增指令指针,这意味着在它重置到列表开头的情况下,它实际上将从列表中的第二项开始(它重置到开头并增加一个分数)
          【解决方案13】:

          C#:

          整洁版:

          using System;
          using System.Collections.Generic;
          using System.Linq;
          using System.Text;
          
          namespace Test
          {
              class Program
              {
                  static void Main(string[] args)
                  {
                      int ip = 1;
                      decimal reg = Convert.ToInt32(args[0]);
                      while (true)
                      {
                          if (ip+1 > args.Length)
                          {
                              break;
                          }
                          decimal curfrac = Convert.ToDecimal(args[ip]) / Convert.ToDecimal(args[ip+1]);
                          if ((curfrac * reg) % 1 == 0)
                          {
                              ip = 1;
                              reg = curfrac * reg;
                          }
                          else
                          {
                              ip += 2;
                          }
                      }
                      Console.WriteLine(reg);
                      Console.ReadKey(true);
                  }
              }
          }
          

          缩减版本的重量为 201 个字符(没有命名空间声明或任何其他内容,只有单个 using 语句(不是系统)和 Main 函数):

          using System;namespace T{using b=Convert;static class P{static void Main(string[] a){int i=1;var c=b.ToDecimal(a[0]);while(i+1<=a.Length){var f=b.ToDecimal(a[i])/b.ToDecimal(a[i+1]);if((f*c)%1==0){i=1;c*=f;}else{i+=2;}}Console.Write(c);}}}
          

          示例(通过命令行参数输入):

          input: 108 3 2
          output: 243.00
          input: 1296 3 2
          output: 6561.0000
          input: 108 455 33 11 13 1 11 3 7 11 2 1 3
          output: 45045.000000000000000000000000
          

          【讨论】:

          • 您可以通过将i+1&lt;=a.Length 作为while 语句中的条件并写入c*=f 而不是c=f*c 来保存一些字符
          • 不会计算诸如“使用”、“命名空间”和“类”之类的东西。您应该只计算完成所有工作的函数中的字符。其他不重要的事情。您也可以删除“if”和“else”中不必要的大括号
          • 我也会转储Console.Read() 命令。从命令提示符运行可执行文件,您将能够看到结果。
          • 谢谢各位,已经低于 200 了,不过现在才勉强!
          • 您可以使用Console.Write 而不是Console.WriteLine 多保存四个字符
          【解决方案14】:

          Javascript 之一:99 个字符。没有奖金向量:(

          function g(n,p,q,i,c){i=0;while(q=p[i],c=n*q[0],(c%q[1]?++i:(n=c/q[1],i=0))&lt;p.length){};return n;};

          输入格式为[[a,b],[c,d]]。我利用了 Javascript 的宽大优势:您可以添加任意数量的参数,而不是 var x=0, y=0;。你是否真的通过它们并不重要,因为它们默认为null

          漂亮的版本:

          函数 g(n,p) { 变量 q, c, i=0; 而(i

          【讨论】:

          • 不,它们默认为undefined,这略有不同。
          • 另外,结尾的分号是不必要的。
          • @bcat:确实,它们默认为undefined。幸运的是,在这种情况下这并不重要,因为无论如何我在使用之前都会分配值。谢谢提醒。
          【解决方案15】:

          Python - 53

          感谢保罗的改进

          f=lambda n,c:next((f(n*x,c)for x in c if x*n%1==0),n)
          

          测试用例

          from fractions import Fraction as fr
          assert f(108, [fr(3, 2)]) == 243
          assert f(1296, [fr(3, 2)]) == 6561
          assert f(108, [fr(455, 33), fr(11, 13), fr(1, 11), fr(3, 7), fr(11, 2), fr(1, 3)]) == 15625
          assert f(60466176, [fr(455, 33), fr(11, 13), fr(1, 11), fr(3, 7), fr(11, 2), fr(1, 3)]) == 7888609052210118054117285652827862296732064351090230047702789306640625
          

          Python - 54 不使用分数

          f=lambda n,c:next((f(n*i/j,c)for i,j in c if n%j<1),n)
          

          Python - 55

          这个有点理论。前两种情况运行正常,但其他两种情况因递归深度而失败。也许有人可以让它与生成器表达式一起工作

          f=lambda n,c:([f(n*i/j,c)for i,j in c if n%j<1]+[n])[0]
          

          这是一种可能性,但即使不包括导入也会增长到 65

          from itertools import chain
          f=lambda n,c:(chain((f(n*i/j,c)for i,j in c if n%j<1),[n])).next()
          

          【讨论】:

          • 53: g=lambda n,c:next((g(n*x,c)for x in c if x*n%1==0),n)
          • 54 如果你坚持不使用分数g=lambda n,c:next((g(n*i/j,c)for i,j in c if n%j&lt;1),n)
          • @Paul..brilliant, next 在这里很完美
          【解决方案16】:

          Perl 6:77 个字符(实验性)

          sub f(@p,$n is copy){
          loop {my$s=first {!($n%(1/$_))},@p or return $n;$n*=$s}}
          

          换行是可选的。调用为:

          say f([3/2], 1296).Int;
          say f([455/33, 11/13, 1/11, 3/7, 11/2, 1/3], 60466176).Int;
          

          可读版本:

          sub Fractran (@program, $state is copy) {
            loop {
              if my $instruction = first @program:
                -> $inst { $state % (1 / $inst) == 0 } {
                $state *= $instruction;
              } else {
                return $state.Int;
              }
            }
          }
          

          注意事项:

          1. 冒号符号first @program: pointy-sub 不适用于当前的实现;第一个 BLOCK,必须使用 @program。

          2. Rakudo 似乎有一个错误的Rat 给出不正确的结果。当前 Niecza 正确快速地运行所有测试程序,包括“奖励”部分。

          【讨论】:

            【解决方案17】:

            方案:326

            我认为需要提交方案,以求平价。我也只是想找个借口玩它。 (请原谅我的初级知识,我相信这可以优化,我愿意接受建议!)

            #lang scheme
            (define fractran_interpreter
            (lambda (state pc program)
                (cond
                    ((eq? pc (length program)) 
                        (print state))
                    ((integer? (* state (list-ref program pc)))
                        (fractran_interpreter (* state (list-ref program pc)) 0 program))
                    (else
                        (fractran_interpreter state (+ pc 1) program)))))
            

            测试:

            (fractran_interpreter 108 0 '(3/2))

            (fractran_interpreter 60466176 0 '(455/33 11/13 1/11 3/7 11/2 1/3))

            我得到了奖励向量! (使用 Dr. Scheme,分配 256 mb)

            【讨论】:

              【解决方案18】:

              Haskell:116 109 个字符

              f p x[]=x
              f p x((n,d):b)|x*n`mod`d==0=f p(x*n`div`d)p|True=f p x b
              main=do{p<-readLn;x<-readLn;print$f p x p}
              

              这最终在某种程度上模仿了达里奥的参赛作品。

              【讨论】:

                【解决方案19】:

                F#:80 个字符

                let rec f p=function|x,(e,d)::t->f p (if e*x%d=0I then(e*x/d,p)else(x,t))|x,_->x
                

                这是一个使用match pattern with |cases 而不是function 的扩展版本:

                //program' is the current remainder of the program
                //program is the full program
                let rec run program (input,remainingProgram) =
                    match input, remainingProgram with
                        | x, (e,d)::rest -> 
                            if e*x%d = 0I then //suffix I --> bigint
                                run program (e*x/d, program) //reset the program counter
                            else    
                                run program (x, rest) //advance the program
                        | x, _ -> x //no more program left -> output the state   
                

                测试代码:

                let runtests() = 
                    [ f p1 (108I,p1) = 243I
                      f p1 (1296I,p1) = 6561I
                      f p2 (108I,p2) = 15625I
                      f p2 (60466176I,p2) = pown 5I 100] 
                

                和结果(在 F# 交互中测试):

                > runtests();;
                val it : bool list = [true; true; true; true]
                

                编辑让我们玩得更开心,并计算一些素数(请参阅起始帖子中的链接页面)。我编写了一个新函数g,它产生状态的中间值。

                //calculate the first n primes with fractran
                let primes n = 
                    let ispow2 n = 
                        let rec aux p = function
                            | n when n = 1I -> Some p
                            | n when n%2I = 0I -> aux (p+1) (n/2I)
                            | _ -> None
                        aux 0 n
                
                    let pp = [(17I,91I);(78I,85I);(19I,51I);(23I,38I);(29I,33I);(77I,29I);(95I,23I); 
                              (77I,19I);(1I,17I);(11I,13I);(13I,11I);(15I,14I);(15I,2I);(55I,1I)]
                
                    let rec g p (x,pp) =
                        seq { match x,pp with 
                                |x,(e,d)::t -> yield x
                                               yield! g p (if e*x%d=0I then (e*x/d,p) else (x,t))
                                |x,_ -> yield x }
                
                    g pp (2I,pp)
                    |> Seq.choose ispow2
                    |> Seq.distinct
                    |> Seq.skip 1 //1 is not prime
                    |> Seq.take n
                    |> Seq.to_list
                

                需要 4.7 秒才能吐出前 10 个素数:

                > primes 10;;
                Real: 00:00:04.741, CPU: 00:00:04.005, GC gen0: 334, gen1: 0, gen2: 0
                val it : int list = [2; 3; 5; 7; 11; 13; 17; 19; 23; 29]
                

                毫无疑问,这是我写过的最奇怪和最慢的素数生成器。我不确定这是好事还是坏事。

                【讨论】:

                  【解决方案20】:

                  Fractran - 1779 个分数

                  (编辑:已修复)

                  (我希望人们仍在关注这个帖子,因为这需要一段时间!)

                  看来SO不会让我发布这么长的东西,所以我发布了Fractran源here

                  输入指定如下:

                  首先,我们将分数m/n = p_0^a0... p_k^ak 编码为:

                  • 从 1 开始。然后,对于每个 ai
                  • 如果ai &gt; 0,则乘以p_2i^ai
                  • 如果a_i &lt; 0,则乘以p_2i+1^{-ai}

                  这样,我们将任何分数编码为正整数。现在,给定一个程序(编码分数 F0、F1、...的序列),我们将其编码为

                  p_0^F0 p1^F1 ...
                  

                  最后,解释器的输入由以下方式给出:

                  2^(program) 3^(input) 5
                  

                  programinput 的编码如上。例如,在第一个测试问题中,3/2 被编码为15,因此程序被编码为2^15;并且108 被编码为500。所以,我们通过了

                  2^{2^15} 3^500 5
                  

                  到程序。输出,然后是形式

                  2^(program) 3^(output)
                  

                  所以在第一个例子中,它会是

                  2^{2^15} 3^3125
                  

                  它是如何工作的?

                  我编写了一种可编译为 Fractran 的元语言。它允许函数(简单的 Fractran 和其他函数的序列),以及 while 循环和 if 语句(为方便起见!)。代码可以在here找到。

                  如果您想自己将该代码编译为 Fractran,可以在 here [tar.gz] 找到我的 (C++) 程序。在令人惊叹的 dogfooding(和炫耀)展示中,我使用了我的 C++ YAML 解析器 yaml-cpp,因此您必须下载并链接到它。对于 yaml-cpp 和“编译器”,您需要 CMake 来生成跨平台的 makefile。

                  这个程序的用法是:

                  ./fracc interpreter.frp
                  

                  它从标准输入读取函数的名称,并将相应的“伪分数”(我将在稍后解释)写入标准输出。所以要编译解释器(解释函数),你可以运行

                  echo "Interpret" | ./fracc interpreter.frp > interpret
                  

                  输出(“pseudo-Fractran”)将是一系列行,每行都有一串以空格分隔的数字。一行对应一个分数:如果行中的nth 数字是an,那么分数就是p_n^an 的乘积。

                  将其转换为 Fractran 非常容易,但如果您比较懒惰,可以使用 to-fractions.py。 [注意:之前我有一个 C++ 程序,我不小心忽略了整数溢出。我将它翻译成 Python 以避免这种情况。]

                  关于输入的注意事项:如果您想以这种方式测试不同的函数,约定总是相同的。它在伪 Fractran 中有许多参数(通常函数上方的注释解释了这一点),所以给它它想要的,再加上一个 1 在下一个插槽(所以在普通 Fractran 中,乘以第一个素数它不会使用)。这是函数开始执行的“信号”位。


                  然而,

                  我不建议实际尝试运行 Fractran 解释器(唉)。我测试了它的许多组件,例如,函数IncrementPrimes,它接受一对素数并返回接下来的两个素数,运行大约需要 8 分钟,使用我愚蠢的 C++ 解释器(无需发布:)。另外,它(至少)在函数调用的数量上呈二次方 - 函数调用的数量增加一倍使得它至少需要四倍的时间(如果有 while 循环或 if 语句,则更多)。所以我猜测运行解释器至少需要几天,如果不是几年的话:(

                  那么我怎么知道它有效呢?好吧,当然我不是 100% 确定,但我非常接近。首先,我测试了它的很多很多组件,特别是,我非常彻底地测试了元语言的所有元素(函数序列和ifwhile 语句)。

                  此外,元语言很容易翻译成您喜欢的语言,甚至更容易翻译成 C++,因为函数的所有参数都是通过引用传递的。如果你又懒了,可以下载我的翻译here[tar.gz](没有makefile,只有两个.cpp文件,直接调用gcc就可以了)。

                  因此您可以比较两个解释器,运行 C++ 版本(它也接受伪 Fractran 的输入/输出),检查它是否有效,然后说服自己元语言也有效。


                  或者!

                  如果你受到启发,并且真的希望看到这个解释器被解释,你可以根据我们得到的 Fractran 输出类型编写一个“聪明”的 Fractran 解释器。输出是非常结构化的——函数序列是使用信号实现的,所以如果你以某种方式缓存解释器所在的位置,如果没有任何重要变化,你可以立即跳转到那里。我认为,这将大大加快程序的速度(也许将运行时间缩短一个或多个幂)。

                  但是,我不太确定如何做到这一点,而且我对所做的事情感到满意,所以我将把它作为练习留给读者。

                  【讨论】:

                  • 哇。真是令人印象深刻。仍在尝试让测试向量运行 - 如果运行,您将获得 500 分。 ;) 你用什么将元语言编译成 fractran 语句?
                  • 因此,使用 Useless 的 Python 实现,以及您建议的测试向量,我得到的输出不是预期的输出:data = (2**(215))*(3500)*5;输出 = f(数据,程序);预期 = (2**(215))*(33125) # 输出 != 预期和输出 % 预期 != 0。怎么了?
                  • 嗯...查看我的最新更新 - 我没想到它会真正完成(它没有使用我的 C++ 解释器)。花了多长时间?另外,它给出的实际输出是什么?
                  • 我应该补充一下,当我要求输出时,你能给我质因数分解吗?
                  • @Nick,我发现了问题——我转换为 Fractran 忽略了整数溢出(愚蠢的 C++)。我将最后一步翻译成 Python,并重新上传了主要的 Fractran 代码。你会注意到它有很多更大的数字!
                  【解决方案21】:

                  方案 73 个字符

                  我的第一次尝试是使用完全标准的 R5RS 方案进行此操作,结果为 104 个字符:

                  (define(f p n)(let l((q p)(n n))(if(null? q)n(let((a(* n(car q))))(if(整数? a)(l p a)(l(cdr q)n))))))

                  针对测试向量中的几个项目运行:

                  > (f'(3/2) 1296) 6561 > (f'(455/33 11/13 1/11 3/7 11/2 1/3) 60466176) 7888609052210118054117285652827862296732064351090230047702789306640625

                  如果您假设 λ 绑定到 lambda 并且定义了 let/cc(就像它们在 PLT 方案中一样;请参阅下文以了解在未定义这些方案的方案中运行它的定义),那么我可以将Jordan's second Ruby solution 改编为Scheme,得到73 个字符(请注意,参数顺序与我的第一个解决方案相反,但与Jordan 的相同;在此版本中,节省了一个字符)。:

                  (define(f n p)(let/cc r(map(λ(i)(if(整数?(* n i))(r(f(* n i)p))))p)n))

                  如果我没有预定义 λlet/cc,那么这个字符有 111 个字符(如果定义了相当常见的 call/cc 缩写,则为 88 个字符):

                  (define(f n p)(call-with-current-continuation(lambda(r)(map(lambda(i)(if(integer?(* n i))(r(f(* n i)p))))p)n)))

                  λlet/cc 的定义:

                  (定义语法 λ (语法规则() ((_ .body) (lambda .body))) (定义语法 let/cc (语法规则() ((_ var .body) (call-with-current-continuation (lambda (var) .body)))))

                  【讨论】:

                    【解决方案22】:

                    Python 中的参考实现

                    此实现对素数分解进行操作。

                    首先,它通过将分子和分母编码为 (idx, value) 元组列表来解码分数元组列表,其中 idx 是素数的数量(2 是素数 0,3 是素数 1,依此类推第四次)。

                    当前状态是每个素数的指数列表,按索引排列。执行指令需要首先遍历分母,检查索引状态元素是否至少为指定值,如果匹配,则减少分母中指定的状态元素,并增加分子中指定的状态元素。

                    这种方法大约是在 Python 中对大整数进行算术运算的速度的 5 倍,并且更容易调试!

                    通过构造一个数组来提供进一步的优化,该数组将每个质数索引(变量)映射到第一次在分数的分母中检查它,然后使用它来构造一个“jump_map”,包括下一条指令执行程序中的每条指令。

                    def primes():
                      """Generates an infinite sequence of primes using the Sieve of Erathsones."""
                      D = {}
                      q = 2
                      idx = 0
                      while True:
                        if q not in D:
                          yield idx, q
                          idx += 1
                          D[q * q] = [q]
                        else:
                          for p in D[q]:
                            D.setdefault(p + q, []).append(p)
                          del D[q]
                        q += 1
                    
                    def factorize(num, sign = 1):
                      """Factorizes a number, returning a list of (prime index, exponent) tuples."""
                      ret = []
                      for idx, p in primes():
                        count = 0
                        while num % p == 0:
                          num //= p
                          count += 1
                        if count > 0:
                          ret.append((idx, count * sign))
                        if num == 1:
                          return tuple(ret)
                    
                    def decode(program):
                      """Decodes a program expressed as a list of fractions by factorizing it."""
                      return [(factorize(n), factorize(d)) for n, d in program]
                    
                    def check(state, denom):
                      """Checks if the program has at least the specified exponents for each prime."""
                      for p, val in denom:
                        if state[p] < val:
                          return False
                      return True
                    
                    def update_state(state, num, denom):
                      """Checks the program's state and updates it according to an instruction."""
                      if check(state, denom):
                        for p, val in denom:
                          state[p] -= val
                        for p, val in num:
                          state[p] += val
                        return True
                      else:
                        return False
                    
                    def format_state(state):
                      return dict((i, v) for i, v in enumerate(state) if v != 0)
                    
                    def make_usage_map(program, maxidx):
                      firstref = [len(program)] * maxidx
                      for i, (num, denom) in enumerate(program):
                        for idx, value in denom:
                          if firstref[idx] == len(program):
                            firstref[idx] = i
                      return firstref
                    
                    def make_jump_map(program, firstref):
                      jump_map = []
                      for i, (num, denom) in enumerate(program):
                        if num:
                          jump_map.append(min(min(firstref[idx] for idx, val in num), i))
                        else:
                          jump_map.append(i)
                      return jump_map
                    
                    def fractran(program, input, debug_when=None):
                      """Executes a Fractran program and returns the state at the end."""
                      maxidx = max(z[0] for instr in program for part in instr for z in part) + 1
                      state = [0]*maxidx
                    
                      if isinstance(input, (int, long)):
                        input = factorize(input)
                    
                      for prime, val in input:
                        state[prime] = val
                    
                      firstref = make_usage_map(program, maxidx)
                      jump_map = make_jump_map(program, firstref)
                    
                      pc = 0
                      length = len(program)
                      while pc < length:
                        num, denom = program[pc]
                        if update_state(state, num, denom):
                          if num:
                            pc = jump_map[pc]
                          if debug_when and debug_when(state):
                            print format_state(state)
                        else:
                          pc += 1
                    
                      return format_state(state)
                    

                    【讨论】:

                      【解决方案23】:

                      Fractran:84 个分数

                      FTEVAL = [197*103/(2^11*101), 101/103, 103*127/(2*101), 101/103, 109/101,
                        2*23/(197*109), 109/23, 29/109,197*41*47/(31*59), 11^10*53/(127*197), 197/53,
                        37/197, 7^10*43/(11^10*37), 37/43, 59/(37*47), 59/47, 41*61/59, 31*67/(41*61),
                        61/67, 7*67/(127*61), 61/67,101/71, 73/(127^9*29), 79/(127^2*73),
                        83/(127*73), 89/(2*29), 163/29, 127^11*89/79, 337/83, 2*59/89, 71/61,
                        7*173/(127*163), 163/173, 337*167/163, 347/(31*337), 337/347, 151/337,
                        1/71,19*179/(3*7*193), 193/179, 157/(7*193), 17*181/193, 7*211/(19*181),
                        181/211, 193/181, 157/193, 223/(7*157), 157/223, 281*283/239,
                        3*257*269/(7*241), 241/269, 263/241, 7*271/(257*263), 263/271, 281/263,
                        241/(17*281), 1/281, 307/(7*283), 283/307, 293/283, 71*131/107, 193/(131*151),
                        227/(19*157), 71*311/227, 233/(151*167*311), 151*311/229, 7*317/(19*229),
                        229/317, 239*331/217, 71*313/157, 239*251/(151*167*313), 239*251/(151*313),
                        149/(251*293), 107/(293*331), 137/199, 2^100*13^100*353/(5^100*137),
                        2*13*353/(5*137), 137/353, 349/137, 107/349, 5^100*359/(13^100*149),
                        5*359/(13*149), 149/359, 199/149]
                      

                      这完全是手写的。我确实编了一种伪语言,以便能够更清楚地表达事物,但我没有编写编译器,而是选择直接编写优化的 Fractran 代码。

                      FTEVAL 接受输入 3^initial_state * 5^encoded_program * 199,产生中间结果 3^interpreted_program_state * 199,并在可被 233 整除的数字处完全停止。

                      解释的程序作为一个以 10 为基数的数字列表嵌入到单个以 11 为基数的数字中,使用数字“a”来标记边界,但最后除外。加法程序[3/2]编码为

                      int("3a2", 11) = 475.
                      

                      乘法程序 [455/33, 11/13, 1/11, 3/7, 11/2, 1/3] 编码为

                      int("1a3a11a2a3a7a1a11a11a13a455a33", 11) = 3079784207925154324249736405657
                      

                      这是一个非常大的数字。

                      第一个测试向量在不到一秒内完成,在 4545 次迭代后产生了预期的结果,并在 6172 次迭代后停止。这里是the complete output

                      不幸的是,当我尝试第二个测试向量时,sage 出现了段错误(但我认为它可以在 Nick 使用素指数向量的实现下工作)。

                      这里的篇幅实在太小,无法解释一切。但这是我的伪代码。我会在几天内写下我的过程,希望如此。

                      # Notations:
                      # %p
                      #     designates the exponent of prime factor p that divides the
                      #     current state.
                      # mov x y
                      #     directly translates to the fraction y/x; its meaning: test if x divides
                      #     the current state, if so divide the current state by x and multiply it by
                      #     y.  In effect, the prime exponents of x and y are exchanged.  A Fractran
                      #     program only comprises of these instructions; when one is executable, the
                      #     program continues from the beginning.
                      # dec x => mov x, 1
                      #     wipes out all factors of x
                      # inc x => mov 1, x
                      #     this form is here for the sake of clarity, it usually appears in a
                      #     loop's entry statement and is merged as such when compiled
                      # sub n ret m {...}
                      #     conceptually represents a Fractran sub-program, which will execute just
                      #     like a normal Fractran program, that is, the sub-program's statements
                      #     execute when triggered and loop back.  The sub-program only exits when none of
                      #     its statement is executable, in which occasion we multiply the program's
                      #     state by m.  We can also explicitly break out early on some conditions.
                      #     It is also possible to enter a sub-prorgram via multiple entry points and
                      #     we must take care to avoiding this kind of behavior (except in case where
                      #     it is desirable).
                      
                      # entry point 101: return 29
                      # Divide %2 modulo 11:
                      #   - quotient is modified in-place
                      #   - remainder goes to %127
                      sub 101 ret 101 { mov 2^11, 197 }
                      sub 101 ret 109 { mov 2, 127 }
                      sub 109 ret 29 { mov 197, 2 }
                      
                      # entry point 59: return 61
                      # Multiply %127 by 10^%31 then add result to %7,
                      # also multiply %31 by 10 in-place.
                      sub 59 ret 41*61 {
                        mov 31, 197*41
                        sub 197 ret 37 { mov 127, 11^10 }
                        sub 37 { mov 11^10, 7^10 }
                      }
                      sub 61 ret 61 { mov 41, 31 }
                      sub 61 ret 61 { mov 127, 7 } # the case where %31==0
                      
                      # entry point 71: return 151 if success, 151*167 if reached last value
                      # Pop the interpreted program stack (at %2) to %7.
                      sub 71 {
                        # call sub 101
                        inc 101
                      
                        # if remainder >= 9:
                        mov 29*127^9, 73
                        #     if remainder == 11, goto 79
                        mov 73*127^2, 79
                        #     else:
                        #         if remainder == 10, goto 83
                        mov 73*127, 83
                        #         else:
                        #             if quotient >= 1: goto 89
                        mov 29*2, 89
                        #             else: goto 163
                        mov 29, 163
                      
                        # 79: restore remainder to original value, then goto 89
                        mov 79, 127^11*89
                      
                        # 83: reached a border marker, ret
                        mov 83, 337
                      
                        # 89: the default loop branch
                        # restore quotient to original value, call 59 and loop when that rets
                        mov 2*89, 59
                        mov 61, 71
                      
                        # 163: reached stack bottom,
                        # ret with the halt signal
                        sub 163 ret 337*167 { mov 127, 7 }
                      
                        # 337: clean up %31 before ret
                        sub 337 ret 151 { dec 31 }
                      }
                      
                      # entry point 193, return 157
                      # Divide %3 modulo %7:
                      #   - quotient goes to %17
                      #   - remainder goes to %19
                      sub 193 ret 17*181 {
                        mov 3*7, 19
                      }
                      mov 7*193, 157
                      sub 181 ret 193 { mov 19, 7 }
                      mov 193, 157
                      sub 157 ret 157 { dec 7 }
                      
                      # entry point 239: return 293
                      # Multiply %17 by %7, result goes to %3
                      mov 239, 281*283
                      sub 241 { mov 7, 3*257 }
                      sub 263 ret 281 { mov 257, 7 }
                      mov 281*17, 241
                      sub 283 ret 293 { dec 7 }
                      
                      # entry point 107: return 149 if success, 233 if stack empty
                      # Pop the stack to try execute each fraction
                      sub 107 {
                        # pop the stack
                        inc 71*131
                      
                        # 151: popped a value
                        # call divmod %3 %7
                        mov 131*151, 193
                      
                        # if remainder > 0:
                        mov 19*157, 227
                        #     pop and throw away the numerator
                        mov 227, 71*311
                        #     if stack is empty: halt!
                        mov 151*167*311, 233
                        #     else: call 239 to multiply back the program state and gave loop signal
                        mov 151*311, 229
                        sub 229 ret 239*331 { mov 19, 7 }
                        # else: (remainder == 0)
                        #     pop the numerator
                        mov 157, 71*313
                        #     clear the stack empty signal if present
                        #     call 239 to update program state and gave ret signal
                        mov 151*167*313, 239*251
                        mov 151*313, 239*251
                      
                        # after program state is updated
                        # 313: ret
                        mov 293*251, 149
                        # 331: loop
                        mov 293*331, 107
                      }
                      
                      # main
                      sub 199 {
                        # copy the stack from %5 to %2 and %13
                        sub 137 ret 137 { mov 5^100, 2^100*13^100 }
                        sub 137 ret 349 { mov 5, 2*13 }
                      
                        # call sub 107
                        mov 349, 107
                      
                        # if a statement executed, restore stack and loop
                        sub 149 ret 149 { mov 13^100, 5^100 }
                        sub 149 ret 199 { mov 13, 5 }
                      }
                      

                      【讨论】:

                      • 哇——太壮观了。你能描述一下如何得出这个答案吗?我很想看看!
                      • 刚看到这个,好漂亮!
                      【解决方案24】:

                      我还不能离开 cmets,但这里是 RCIX 的 C# 版本的“稍微”短版本(我相信它短了 7 个字符)

                      using System;namespace T{static class P{static void Main(string[] a){int i=1;Func<string,decimal> d=Convert.ToDecimal;var c=d(a[0]);while(i+1<=a.Length){var f=d(a[i])/d(a[++i]);if((f*c)%1==0){i=1;c*=f;}else i++;}Console.Write(c);}}}
                      

                      哪个使用

                      Func<string,decimal> d=Convert.ToDecimal
                      

                      并调用d(); 而不是

                      using b=Convert;
                      

                      并反复拨打b.ToDecimal();

                      我还删除了 else 语句周围的一对不必要的花括号,以获得 1 个字符 :)。

                      我还将a[i+1] 替换为a[++i] 并在下面的else 正文中我将i+=2 替换为i++ 以获得另一个字符:P

                      【讨论】:

                        【解决方案25】:

                        有点晚了... dc 84 字符

                        dc 解决方案(OpenBSD)只是为了好玩

                        [ss1sp]s1[Rlp1+sp]s2?l1xz2/sz[z2/ds_:bl_:az0<L]dsLx
                        1[;als*lp;b~0=1e2lpdlz!<L]dsLxlsp
                        

                        它处理所有情况:

                        $ dc fractran.dc  
                        455 33 11 13 1 11 3 7 11 2 1 3 60466176
                        7888609052210118054117285652827862296732064351090230047702789306640625
                        

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 2010-12-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2010-11-28
                          • 2011-02-08
                          • 2010-10-24
                          相关资源
                          最近更新 更多