代码高尔夫:Fractran

49

挑战

编写一个程序,作为Fractran解释器。以字符计数最少的方式实现,无论是哪种语言,都是获胜者。您的程序必须接收两个输入:要执行的fractran程序和输入整数n。程序可以采用任何方便您的程序的形式 - 例如,2元组列表或平面列表。输出必须是单个整数,即执行结束时寄存器的值。

Fractran

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

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

有关Fractran的工作原理和原因的详细信息,请参见esolang entry和good math/bad math上的this 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(56

奖励测试向量:

如果您能正确执行最后一个程序,则不需要提交此答案。但是,如果可以,那就太棒了!

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

提交与评分

程序将按照字符长度严格排名,最短的为最佳。可以同时提交美观且有文档的代码和“minified”版本的代码,以便人们了解情况。

不接受“J”语言。这是因为在链接页面中已经有一个众所周知的J解决方案。 如果您是J粉丝,很抱歉!

额外奖励,任何能够提供一个自托管的fractran解释器的人将获得500声望点奖励。如果存在多个自托管解释器,则具有最少分数的解释器将获得赏金。

获胜者

官方获胜者是Jesse Beder's solution,他提交了一个由1779个分数构成的自托管fractran解决方案。但实际上,该解决方案的执行速度太慢,甚至无法执行1+1。

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

荣誉提名:

  • Stephen Canon's solution,使用165个字符的x86汇编语言(28个字节的机器代码)
  • Jordan's solution,使用52个字符的ruby - 可处理长整数
  • Useless's solution,使用87个字符的Python,虽然不是最短的Python解决方案之一,但是它是少数不是递归的解决方案之一,因此可以轻松处理更难的程序。它也非常易读。

20
对于不可避免的关门派: 在 Stack Overflow 上有超过100个标记为代码高尔夫的问题。这是一个合法的帖子,并且会得到有趣而富有启发性的回答。此外,它是一个社区 Wiki,因此没有人会通过它来攫取声誉。 - Nick Johnson
8
@R. Pate:在我之前可以先去和其他 104 个代码高尔夫的人争论一下,或者在 Meta 上提出来,社区共识通常是支持的。@mmyers:谢谢,我会更新问题。 - Nick Johnson
9
目前的共识看起来相当强大(超过20个人支持“可以,如果你不喜欢就忽略它”):http://meta.stackexchange.com/questions/20912/so-weekly-code-golf。当然,你(和其他关心此事的人)应该投票,并且如果你的观点没有被覆盖,也应该说明你的观点。 - ire_and_curses
5
我并非预先发表评论,而是在别人投票关闭之后才进行了评论。 - Nick Johnson
6
我只是发表个人意见:这是一个很棒的问题,一方面因为我喜欢代码高尔夫,另一方面更重要的是因为它让我了解了一个非常有趣的东西(以前从没听说过这种“语言”,但它太棒了!)。 - Edan Maor
显示剩余8条评论
25个回答

45

Fractran - 1779个分数

(编辑:已修复)

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

看起来SO不会让我发布像这样长的东西,所以我在此处发布了Fractran源代码。

输入如下:

首先,我们通过以下方式对分数进行编码:

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

这样,我们将任何分数编码为正整数。 现在,给定一个程序(编码分数F0、F1等的序列),我们通过以下方式进行编码

p_0^F0 p1^F1 ...

最后,将输入提供给解释器:

2^(program) 3^(input) 5

其中programinput按上述方法进行编码。例如,在第一个测试问题中,3/2被编码为15,因此程序被编码为2^15108被编码为500。所以我们通过。

2^{2^15} 3^500 5

程序的输出形式如下:

2^(program) 3^(output)

在第一个示例中,它将会是

2^{2^15} 3^3125

它是如何工作的?

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

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

该程序的用法是:

./fracc interpreter.frp

它从标准输入读取函数名称,并将相应的“伪分数”(我马上解释)写入标准输出。因此,要编译解释器(Interpret函数),您可以运行以下命令:

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

输出结果(“伪Fractran”)将是一系列带有空格分隔数字的行。每一行对应一个分数:如果该行中的第n个数字是an,则该分数是pn^an的乘积。
这很容易转换为Fractran,但如果你懒得这样做,可以使用to-fractions.py。[注意:我之前有一个C++程序,我粗心地忽略了整数溢出。我将其翻译成Python以避免这种情况。]
关于输入的说明:如果你想用这种方式测试不同的函数,约定总是相同的。它有许多参数(通常函数上面的注释会解释这一点),在伪Fractran中,所以给它它想要的东西,再加上下一个位置上的1(因此在普通的Fractran中,乘以它不会使用的第一个质数一次)。这是一个“信号”位,告诉函数开始运行。

然而,

我不建议实际尝试运行Fractran解释器(唉)。我测试了它的许多组件,例如函数IncrementPrimes,它接受一对质数并返回下两个质数,使用我的愚蠢的C ++解释器需要大约8分钟才能运行(不需要发布那个:)。此外,它在函数调用次数方面至少呈二次增长 - 函数调用次数翻倍会使其至少花费四倍时间(如果有while循环或if语句,则耗时更长)。因此,我猜测运行解释器将需要至少几天,甚至可能是几年:(

那么我怎么知道它可以工作?当然,我不是100%确定,但我非常确定。首先,我测试了许多许多其组件,特别是非常彻底地测试了元语言的所有元素(函数序列和ifwhile语句)。

此外,元语言易于转换为您喜欢的语言,甚至更容易转换为C ++,因为所有函数参数都通过引用传递。如果您再次感到懒惰,可以在此处下载我的翻译here [tar.gz](没有makefile;只有两个.cpp文件,因此直接调用gcc即可)。

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


或者!

如果你感到灵感涌现,而且真的想看到这个解释器被解释,你可以编写一个基于我们得到的Fractran输出类型的“聪明”Fractran解释器。输出非常有结构 - 函数序列使用信号实现,因此如果你以某种方式缓存了解释器所在的位置,则在没有重要更改的情况下可以立即跳转到那里。我认为,这将极大地加速程序(可能会将运行时间缩短一次或多次幂)。

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


哇,真是令人印象深刻。我还在努力让测试向量运行-如果成功了,你就可以得到500分。 ;)你用什么编译器将元语言编译成fractran语句的? - Nick Johnson
所以,使用Useless的Python实现,使用您建议的测试向量,我得到的输出不是预期的输出:data = (2 *(2 ** 15))(3 ** 500)* 5; output = f(data,program); expected =(2 *(2 ** 15))(3 ** 3125)# output!= expected且output%expected!= 0。出了什么问题? - Nick Johnson
@Nick,我找出了问题——我的 Fractran 转换忽略了整数溢出(该死的 C++)。我将最后一步转换为 Python,并重新上传了主要的 Fractran 代码。您会发现它有 很大 的数字! - Jesse Beder
尝试在输入2^(2^15) 3^10 5(使用输入2^1 3^1的程序3/2的修正版本)上运行修正版程序,大约需要2个半小时才能得出输出为2^(2^15) 3^10的结果。有什么想法,为什么输出似乎没有被修改? - Nick Johnson
@ephemient,我上周得了流感,所以除了躺在床上喝茶和写代码之外,我什么也做不了。这是不公平的优势 :) - Jesse Beder
显示剩余5条评论

41

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 整除的数字处完全停止。

解释程序嵌入在一个单个的基数为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次迭代后停止。这里是完整输出

不幸的是,当我尝试第二个测试向量时,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 }
}

1
哇,太棒了。你能描述一下你是怎么得出那个答案的吗?我很想看看! - Isaac
刚看到这个,非常漂亮! - Stephen Canon

20

x86_64汇编,165个字符(28个字节的机器码)。

状态通过%rdi传递,程序(指向以空字符结尾的分数数组的指针)在%rsi中。按照通常的C样式调用约定,结果将在%rax中返回。使用非标准的调用约定或英特尔语法(这是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

删除注释、多余的空格和冗长的标签_fractran,以获得最小化版本。

16

Ruby, 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

Pretty version (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

使用Rational的Ruby,53 52个字符

受到gnibbler解决方案的启发,我能够将使用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。 - Stephen Canon
1
你可以使用 map 而不是 each,这样它就会在52处绑定。 - John La Rooy
2
def f(n,c)(j=c.find{|i|in%1==0})?f(nj,c):n end - 48 - John La Rooy
2
def f(n,c)c.find{|i|($j=i*n)%1==0}?f($j,c):n end - 48 - John La Rooy

12

Golfscript - 32

{:^{1=1$\%!}?.1={~@\/*^f}{}if}:f
; 108 [[3 2]] f p # 最初的回答 ; 1296 [[3 2]] f p # 最初的回答 ; 108 [[455 33][11 13][1 11][3 7][11 2][1 3]] f p # 最初的回答 ; 60466176 [[455 33][11 13][1 11][3 7][11 2][1 3]] f p # 最初的回答

这段代码是关于Golfscript编程语言的。它的功能是将一些数字进行处理并输出结果。具体的实现方式比较复杂,其中包含了一些特殊的符号和函数,需要一定的编程经验才能理解。

Golfscript看起来像是作弊,但它并没有被排除在外,甚至还获得了额外的向量奖励 - 所以我将其标记为获胜者。 - Nick Johnson

7

Haskell, 102 characters

import List
import Ratio
l&n=maybe n((&)l.numerator.(n%1*).(!!)l)$findIndex((==)1.denominator.(n%1*))l
$ ghci
Prelude> :m List Ratio
Prelude List Ratio> let l&n=maybe n((&)l.numerator.(n%1*).(!!)l)$findIndex((==)1.denominator.(n%1*))l
Prelude List Ratio> [3%2]&108
243
Prelude List Ratio> [3%2]&1296
6561
Prelude List Ratio> [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
前奏 List Ratio> 让 l&n = 如果找到 ((==) 1.分母) 的索引,则应用 (&)l.(*)n.(!!)l,否则应用 n
前奏 List Ratio> [455%33,11%13,1%11,3%7,11%2,1%3]&108
15625 % 1

6

C,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
$ echo 108 3 2 . | ./a.out; echo
243
$ echo 1296 3 2 . | ./a.out; echo
6561
$ echo 108 455 33 11 13 1 11 3 7 11 2 1 3 . | ./a.out; echo
15625
这段代码是一个简单的命令行程序。它通过在终端中输入一组数字,来计算并输出结果。

有趣的语言选择。 - Vlad the Impala
为什么在高尔夫比赛中要使用 long long?普通的 int 会失去那么多精度吗? - Chris Lutz
@Goody: "选择不佳",你的意思是这样。它肯定没有赢的机会。@Chris:第三个例子有一个中间计算,其值为2299171875>2^31。我在最初编写时没有意识到这一点,但我可以重新排列程序来避免这种情况... - ephemient
也许你可以使用 _int64_int128 - John La Rooy
由于假定分数已经化简,我只是交换了除法和乘法的顺序,现在前三个测试都适用于 int :) - ephemient

6

Python, 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

1
你可以省略 or 前面的空格,也不必计算末尾的换行符,这样应该是 68。 - John La Rooy
1
你可以直接在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) - Paul

5

Python - 53

感谢 Paul 的改进

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 不使用Fraction模块

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) 53: g= lambda n,c: next((g(n*x,c) for x in c if x*n%1 == 0), n) - Paul
g=lambda n,c:next((g(n*i/j,c)for i,j in c if n%j<1),n) 的意思是:如果你坚持不使用分数,那么就返回54。 - Paul
@Paul..太棒了,next在这里非常完美。 - John La Rooy

5

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秒的时间:

> 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]

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接