列举押韵方案


26

“押韵方案”是ato 的字母字符串,以z使字符的首次出现按从()开始的升序排列(无间隔)a。例如(标记为首次出现):

abccdbebdcfa
^^^ ^ ^   ^

长度的押韵方案的数目N贝尔数 给出B(N)。(OEIS A000110

挑战

您的任务是实现这些押韵方案的枚举,即从整数到押韵方案的双射映射。您会得到一个正整数N <= 26,以及一个非负整数0 <= i < B(N)。或者,您可以使用range 1 <= i <= B(N)。您应该输出一个length的押韵方案N,以使每一个i产生不同的字符串。

您可以编写程序或函数,通过STDIN(或最接近的替代方案),命令行参数或函数自变量获取输入,并通过STDOUT(或最接近的替代方案),函数返回值或函数(out)参数输出结果。

您可以使用大小写字母(一致)。

您的代码必须能够处理在合理的时间的任何有效输入(例如不超过几个小时N = 26,最坏的情况下i)。N即使在慢速语言中,这也应允许解决方案以小比例扩展(对于小底数),但禁止解决方案以i(即B(N))线性扩展。特别是,这意味着您不能仅遍历所有有效长度的韵律方案,N直到放弃该i方案为止。

适用标准规则。

例子

方案的确切分配i(即给定方案的顺序N)由您决定。但是,如果您选择了字典顺序,则您的解决方案应对应于下表(-表示无效输入):

N\i 1    2    3    4    5    6    7    8    9    10   11   12   13   14   15
1   a    -    -    -    -    -    -    -    -    -    -    -    -    -    -
2   aa   ab   -    -    -    -    -    -    -    -    -    -    -    -    -
3   aaa  aab  aba  abb  abc  -    -    -    -    -    -    -    -    -    -
4   aaaa aaab aaba aabb aabc abaa abab abac abba abbb abbc abca abcb abcc abcd

这是一个简短的CJam脚本,它会为给定的长度生成所有有效的押韵方案(但请不要尝试超过10个,否则您将等待一段时间)。

相关挑战


5
我可能会赏金赏析一个(完善的)多项式时间解(在中N),条件是事实证明这并不是一件很琐碎的事,而我却太愚蠢而找不到它。
Martin Ender

尽管对多项式时间解决方案的赏心悦目,但我仍然希望看到满足时间限制的指数时间解决方案。(我自己的Mathematica参考实现当前仍将赢得挑战。)
Martin Ender

B(26)是不适合64位整数的最小Bell数。卑鄙的 :-(
Anders Kaseorg '16

Answers:


3

CJam,68 66字节

r~:W)1a*{__(;\);_,,.*.+}W(*r~{X@\=_2$\/:CX<!{X:C):X;}&C*-C'a+o}W*;

在线尝试。

这是我的第一个CJam程序。它开始作为我的Perl解决方案的端口开始使用,最初长度超过130个字节。欢迎进一步打高尔夫球。

与我的Perl程序一样,它分为两部分。

Part 1:
r~:W                                         | Read the first input (n) and store it in W
    )1a*                                     | Create an array of n+1 1s
        {              }W(*                  | Repeat n-1 times:
         __                                  | Duplicate array twice
           (;\);                             | Remove first element of 1st array. Swap
                                             | arrays. Remove last element of 2nd array
                _,,                          | Duplicate array. Count items. Create range
                   .*.+                      | Multiply arrays. Add 1st array to result

Part 2:
r~                                           | Read the second input (i)
   {                                  }W*    | Repeat n times:
    X@                                       | Push y (initially 1). Bring item 2 (last array) to top
     \=                                      | Swap top two items. Pop array[y] (v)
       _2$                                   | Duplicate v. Copy item 2 (i) to top
          \/:CX                              | Swap i & v. i/v. Store in C (c). Push y
               <!{       }&                  | If !(i/v < c):
                  X:C):X;                    | c = y. ++y (store in X)
                           C*-C'a+o          | i -= c * v. Push y. Push "a". Add c. Print
                                         ;   | Discard top item (integer 0)

要调试由第1部分创建的数组,请在第1 ]_`o~部分和第2 部分之间添加。如果n为5,则数组将如下所示:[[1 1 1 1 1 1] [1 2 3 4 5] [2 5 10 17] [5 15 37] [15 52]]。没有使用每个数组的0索引,它们只是因为不必计算偏移量而变得更容易。数组的计算如下:

[2 5 10 17] [2 5 10 17] [2 5 10 17]        | Duplicate twice
[2 5 10 17] [2 5 10 17] [5 10 17]          | Discard first item of array
[2 5 10 17] [5 10 17] [2 5 10 17]          | Swap last two arrays
[2 5 10 17] [5 10 17] [2 5 10]             | Discard last item of array
[2 5 10 17] [5 10 17] [2 5 10] [2 5 10]    | Duplicate array
[2 5 10 17] [5 10 17] [2 5 10] 3           | Count items in array
[2 5 10 17] [5 10 17] [2 5 10] [0 1 2]     | Integer to range 0 - n-1
[2 5 10 17] [5 10 17] [0 5 20]             | Multiply arrays [2*0 5*1 10*2]
[2 5 10 17] [5 15 37]                      | Add arrays [5+0 10+5 17+20]

在计算下一个数组时,它会保留旧数组的副本。第2部分以相反的顺序读取和丢弃数组。


13

Python 2,153

u=[1]*999;i=60;exec"u[i]=i%30*u[i-30]+u[i-29];i+=1;"*900
def x(l,n,a=0):m=u[30*l+a];c=n>=a*m;return'.'*l and chr(65+min(n/m,a))+x(l-1,[n%m,n-m*a][c],a+c)

它使用字母顺序和基于0的索引。

让其l表示字母后缀的长度,并a表示在上一部分中使用的不同字母的数量。然后,一个p(l,a)计算剩余字母选择方式的函数可能是40个字节:

p=lambda l,a:l<1or a*p(l-1,a)+p(l-1,a+1)

但是,这对于挑战来说太慢了,因此必须预先计算必要的值并将其存储在u数组中。在计算的每个阶段,如果下一个字母是a已使用的字母,则n = k * p(1-1,a)+ n',其中k是字母的0索引字母,n'n下一个函数调用的值,其中包含有关剩余字母的信息。如果使用新字母,则n = a * p(l-1,a)+ n'


1
最坏情况的输入需要多长时间?
Michael Klein'2

1
@MichaelKlein可以忽略的时间。
feersum '16

这正是我计划要做的(除非我本来会用JS完成的)。不错的工作!+1
ETHproductions '16

11

Haskell(GHC 7.10),150字节

s=(1,\_->[]):s
k!((y,b):l@((x,a):_))|let h i|i<x=k:a i|(p,q)<-divMod(i-x)y=p:b q=(x+k*y,h):(k+1)!l
n#i=(['a'..]!!).fromEnum<$>snd(iterate(0!)s!!n!!0)i

运算符n # i计算ilength 的th(零索引)韵律方案n。它运行在O(n²)(大整数)运算中,利用了Haskell的惰性无限列表进行自动记忆。样品运行:

*Main> 26 # 0
"abcdefghijklmnopqrstuvwxyz"
*Main> 26 # 1
"abcdefghijklmnopqrstuvwxya"
*Main> 26 # 2
"abcdefghijklmnopqrstuvwxyb"
*Main> 26 # 49631246523618756271
"aaaaaaaaaaaaaaaaaaaaaaaabb"
*Main> 26 # 49631246523618756272
"aaaaaaaaaaaaaaaaaaaaaaaaab"
*Main> 26 # 49631246523618756273
"aaaaaaaaaaaaaaaaaaaaaaaaaa"
*Main> [1 # i | i <- [0..0]]
["a"]
*Main> [2 # i | i <- [0..1]]
["ab","aa"]
*Main> [3 # i | i <- [0..4]]
["abc","aba","abb","aab","aaa"]
*Main> [4 # i | i <- [0..14]]
["abcd","abca","abcb","abcc","abac","abaa","abab","abbc","abba","abbb","aabc","aaba","aabb","aaab","aaaa"]

(如果最大N为25而不是26,则.fromEnum可以将其删除,因为B(25)可以容纳64位Int。)


1
看起来很棒。您介意添加较少打高尔夫球的版本以使操作更轻松吗?
Michael Klein'2

4

Perl 257 + 1(-p标志)= 258

Perl 182 + 10(-pMbignum标志)= 192

($n,$i)=split;@m=[@a=(1)x($n+1)];while($a[2]){push@m,[@a=map{$a[$_]*$_+$a[$_+1]}0..$#a-1]}$_='';$y=1;while($w=pop@m){$c=int($i/($v=$$w[$y]));$c=$y++if($c>=$y);$i-=$c*$v;$_.=chr$c+65}

感谢dev-nul节省了很多字节!现在,我根据从CJam版本中学到的知识重写了它。

以字母升序计算韵律,索引为0。

分两部分:第1部分为128 90字节,并为第2部分计算矩阵。第2部分为129 92字节,并做一些简单的数学运算来计算每个字母。如果我可以摆脱矩阵并用两个简单的数字替换它,则可以为每个数字计算通过矩阵的一条路径,并节省大量字节!显然,这个想法行不通!

不幸的是,对于i高于9007199254740992的值,它不会输出正确的韵律,但是对于较低的值,它的效果很好! 我以11个字节的成本添加了Bignum库。它是从命令行运行的perl -pMbignum bell-rhyme.pl-pMbignum = 10个字节。任何输入值也非常快。


2

的Oracle SQL 11.2 412个 284 283字节

WITH a AS(SELECT CHR(96+LEVEL)d,LEVEL b FROM DUAL CONNECT BY LEVEL<=:i),v(s,c,n)AS(SELECT d,1,1 FROM a WHERE b=1 UNION ALL SELECT s||d,b,LENGTH(REGEXP_REPLACE(s||d,'([a-z])\1+','\1'))FROM v,a WHERE(b<=n OR b=c+1)AND LENGTH(s)<:n)SELECT s FROM v WHERE:n=LENGTH(s)AND:i<=:n ORDER BY 1;

不幸的是,它最多只能运行8个长度。任何较大的值都会导致:ORA-01489:字符串连接的结果太长

未打高尔夫球

WITH a AS(SELECT CHR(96+LEVEL)d,LEVEL b FROM DUAL CONNECT BY LEVEL<=:i),
v(s,c,n) AS
(
  SELECT d,1,1 FROM a WHERE b=1
  UNION ALL
  SELECT s||d,b,LENGTH(REGEXP_REPLACE(s||d,'([a-z])\1+','\1')) 
  FROM v,a 
  WHERE (b<=n OR b=c+1) AND LENGTH(s)<:n
)
SELECT s FROM v WHERE LENGTH(s)=:n AND :i<=:n ORDER BY 1;

一个视图在a列中生成:i字母,并在b中生成它们的值。

递归视图v将构造的字符串作为参数v,c中使用的最后一个字母的值,n中使用的最大字母的值。n参数等于没有重复字母的字符串长度,这就是正则表达式的作用。

如果字母的值小于等于已使用的最大字母的值,或者它是要使用的下一个字母,则该字母是有效的。

以某种方式查询需要运行LENGTH(s)<:n部分,在查询的工作方式上我一定缺少一些东西。

主SELECT负责过滤掉无效输入和在达到目标长度之前构建的较短字符串。

412字节版本

WITH a AS(SELECT * FROM(SELECT d,b,ROW_NUMBER()OVER(PARTITION BY b ORDER BY d)l FROM(SELECT CHR(64+DECODE(MOD(LEVEL,:i),0,:i,MOD(LEVEL,:i)))d,CEIL(LEVEL/:i)b FROM DUAL CONNECT BY LEVEL<=:i*:n))WHERE l<=b),v(s,c,p)AS(SELECT d,1,l FROM a WHERE b=1 UNION ALL SELECT s||d,c+1,l FROM v,a WHERE c+1=b AND(l<=LENGTH(REGEXP_REPLACE(s,'([A-Z])\1+','\1'))OR l=p+1))SELECT s FROM v WHERE LENGTH(s)=:n AND :i<=:n ORDER BY 1;

不要尝试使用26来查询412字节的查询。它至少在Macbook上的Docker容器中运行的xe版本上将数据库置于受限模式。我可以在工作中尝试使用数据库云服务器,但可悲的是,我仍然需要工作以维持生计。


0

Mathematica,136个字节

(For[j=2^#-1;t=#2,c=1;m=0;x=t;r=If[#>0,++m,c*=m;d=x~Mod~m+1;x=⌊x/m⌋;d]&/@j~IntegerDigits~2;;c<=t,t-=c;--j];FromCharacterCode[r+64])&

为了完整起见,这是我参考的参考实现。与现有答案相反,它不是在多项式时间内运行(N以2为底的指数),但确实满足了时间限制(最坏的情况仍然会在半小时内运行)。

这个想法是这样的:

  • 对于每种押韵方案,我们可以确定到目前为止最大字符增加的位置:

    ABCDEFGHDIJDEKBBIJEIKHDFII
    ^^^^^^^^ ^^  ^
    

    我们可以将这些标记视为二进制数,这样可以轻松地遍历所有此类结构。我们需要从2 n-1迭代到2 n(或者反过来),这就是指数时间复杂度的来源。

  • 对于每个这样的结构,很容易确定有多少个这样的字符串:只能自由选择标记之间的间距,并且间距前面的最大值告诉我们每个位置有效的字符数。这是一个简单的产品。如果此数字小于i,则从中减去i。否则,我们已经找到了要求的押韵方案的结构。
  • 为了枚举给定结构中的方案,我们仅将其i(或其剩余部分)表示为混合基数,其中数字的权重由剩余位置中允许的字符数确定。

我不知道这是否允许使用提交的其他一些语言的简短解决方案,因为它不需要任何备注或预先计算。

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.