安排泡泡


26

注意,挑战是从math.stackexchange询问的问题复制而来的。

最近,我掌握了一些吹泡泡的技巧。首先,我会像这样吹泡泡: 在此处输入图片说明

但是事情开始变得奇怪了:

在此处输入图片说明

一段时间后,我吹了一些怪异的气泡:

在此处输入图片说明

吹完数百个甚至数千个这样的气泡后,我的额头突然出现问题:给定n个气泡,您可以用几种不同的方式排列它们?例如,如果n = 1,则只有1种排列。如果n = 2,则有2种排列。如果n = 3,则有4种排列。如果n = 4,则有9种排列。

这是4个气泡的9个排列:
在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明 在此处输入图片说明

在吹完所有这些奇妙的泡泡之后,我决定与您分享点算他们的安排的乐趣。因此,这是您的任务:


目标

编写一个程序,函数或类似的程序,计算可以排列n气泡的方式。


输入项

n,气泡数。n> 0


输出量

排列这些气泡的方式的数量。


获奖标准

如果我们能在您的代码周围吹泡泡,那真的很酷。您编写的代码越小,执行起来就越容易。因此,使用最少字节数编写代码的人将赢得比赛。


额外的信息

信息系统


5
如果气泡可能相交,则这是一个未解决的问题,其中n = 4的173个解
orlp

@orlp幸运的是,这些气泡没有相交。
TheNumberOne

1
0一个有效的输入?
Martin Ender

@ KenY-N是的。底部已经有OEIS链接
RomanGräf17年

糟糕!删除愚蠢的评论时间...
Ken YN

Answers:


12

Python 2,92 87字节

a=lambda n:n<2or sum((k%d<1)*d*a(d)*a(n-k)for k in range(1,n)for d in range(1,1+k))/~-n

用简单的英语来说:为了计算,a(n)我们d*a(d)*a(n-k)为每个小于或等于的d正整数的每个除数进行计算,将所有这些相加,然后除以。knn-1

为了使其运行更快,请在Python 3中运行(在上面的函数中替换///)并记住:

import functools
a = functools.lru_cache(None)(a)

如果执行此操作,它将a(50) = 425976989835141038353立即进行计算。


哇,太酷了。我假设lru_cache()记忆功能?
Patrick Roberts

@PatrickRoberts是的,通常将其用作函数装饰器,但您也可以手动将其应用于函数。
orlp

@PatrickRoberts这是的文档lru_cache
下午17年

该函数返回Truen<2。我想这是可以的n=1,因为在Python True中,在数字上下文中计算结果为1,但a(0)应返回0 n<2 and n or sum...
下午17年

我猜可以说有一种方法可以安排零气泡,但这与A000081不一致。OTOH,如果我们只需要解决正数问题,n那么我们可以放心地忽略这种极端情况,因为这不会影响对high的递归调用n
下午17年

10

GNU Prolog,98个字节

b(x,0,x).
b(T/H,N,H):-N#=A+B+1,b(H,A,_),b(T,B,J),H@>=J.
c(X,Y):-findall(A,b(A,X,_),L),length(L,Y).

这个答案是一个很好的例子,说明了Prolog如何甚至可以使用最简单的I / O格式。它通过描述问题而不是解决问题的算法以真正的Prolog风格工作:它指定了什么才算是合法的泡沫安排,要求Prolog生成所有这些泡沫安排,然后对它们进行计数。生成需要55个字符(程序的前两行)。计数和I / O采用其他43(第三行,以及将这两部分分开的换行)。我敢打赌,OP不会导致语言与I / O纠缠,这不是问题!(注意:Stack Exchange的语法突出显示使它更难阅读,而不是更容易,因此我将其关闭了)。

说明

让我们从一个实际上不起作用的类似程序的伪代码版本开始:

b(Bubbles,Count) if map(b,Bubbles,BubbleCounts)
                and sum(BubbleCounts,InteriorCount)
                and Count is InteriorCount + 1
                and is_sorted(Bubbles).
c(Count,NPossibilities) if listof(Bubbles,b(Bubbles,Count),List)
                       and length(List,NPossibilities).

应该很清楚地知道它是如何b工作的:我们通过排序列表(这是多集的简单实现,导致相等的多集比较相等)来表示气泡,单个气泡[]的计数为1,较大的气泡的计数为1等于内部气泡总数加1。对于4的计数,此程序将(如果可行)生成以下列表:

[[],[],[],[]]
[[],[],[[]]]
[[],[[],[]]]
[[],[[[]]]]
[[[]],[[]]]
[[[],[],[]]]
[[[],[[]]]]
[[[[],[]]]]
[[[[[]]]]]

出于某些原因,该程序不适合作为答案,但是最紧迫的是Prolog实际上没有map谓词(编写它会占用太多字节)。因此,相反,我们将程序编写如下:

b([], 0).
b([Head|Tail],Count) if b(Head,HeadCount)
                    and b(Tail,TailCount)
                    and Count is HeadCount + TailCount + 1
                    and is_sorted([Head|Tail]).
c(Count,NPossibilities) if listof(Bubbles,b(Bubbles,Count),List)
                       and length(List,NPossibilities).

这里的另一个主要问题是,由于Prolog评估顺序的工作方式,运行时它将陷入无限循环。但是,我们可以通过重新安排程序来解决无限循环:

b([], 0).
b([Head|Tail],Count) if Count #= HeadCount + TailCount + 1
                    and b(Head,HeadCount)
                    and b(Tail,TailCount)
                    and is_sorted([Head|Tail]).
c(Count,NPossibilities) if listof(Bubbles,b(Bubbles,Count),List)
                       and length(List,NPossibilities).

这看起来可能很怪异-我们先将计数加在一起,然后才知道它们是什么-但是GNU Prolog #=能够处理这种非因果算法,因为它是的第一行b,并且HeadCountTailCount必须都小于Count(已知),它用作自然地限制递归项可以匹配多少次的方法,从而使程序始终终止。

下一步是打高尔夫球。使用单字符变量名称,使用:-for if,for的缩写and,使用setof而不是listof(它具有较短的名称并在这种情况下产生相同的结果)和使用sort0(X,X)而不是is_sorted(X)(因为is_sorted实际上不是真正的函数,我做了)

b([],0).
b([H|T],N):-N#=A+B+1,b(H,A),b(T,B),sort0([H|T],[H|T]).
c(X,Y):-setof(A,b(A,X),L),length(L,Y).

这很短,但是有可能做得更好。关键的见解是,[H|T]列表语法确实非常冗长。正如Lisp程序员所知道的,列表基本上只是由cons单元组成的,而这些单元基本上只是元组,并且该程序的任何部分都几乎没有使用列表内置函数。Prolog有几种非常短的元组语法(我最喜欢的是A-B,但我第二喜欢的是A/B,我在这里使用它是因为在这种情况下,它会产生易于阅读的调试输出);而且我们还可以nil在列表的末尾选择我们自己的单字符,而不是被两个字符所困扰[](我选择了x,但基本上任何方法都可以)。因此,除了[H|T],我们可以使用T/H和从中获取输出b 看起来像这样(请注意,元组的排序顺序与列表的排序顺序略有不同,因此它们的顺序与上面的顺序不同):

x/x/x/x/x
x/x/x/(x/x)
x/(x/x)/(x/x)
x/x/(x/x/x)
x/(x/x/x/x)
x/x/(x/(x/x))
x/(x/x/(x/x))
x/(x/(x/x/x))
x/(x/(x/(x/x)))

这比上面的嵌套列表难读,但是有可能。在思维上跳过xs,并将其解释/()为气泡(或简单地解释为/没有内容的退化气泡,如果()后面没有内容的话),并且元素与上面显示的列表版本具有1对1(如果无序)的对应关系。

当然,此列表表示形式尽管短得多,但仍具有主要缺点。它不是语言内置的内容,因此我们不能sort0用来检查列表是否已排序。sort0但是无论如何,它都是非常冗长的,所以手动执行操作并不会造成很大的损失(实际上,在[H|T]列表表示中手动执行操作的字节数完全相同)。这里的关键见解是,该方案的书面检查,看是否列表进行排序,如果它的尾巴进行排序,如果它的尾巴进行排序,依此类推; 有很多多余的检查,我们可以加以利用。取而代之的是,我们将检查以确保前两个元素是正确的(这确保了在检查列表本身及其所有后缀之后,列表将最终进行排序)。

第一个元素很容易访问;那只是列表的头H。但是,第二个元素很难访问,并且可能不存在。幸运的是,x它少于我们正在考虑的所有元组(通过Prolog的广义比较运算符@>=),因此我们可以认为单例列表的“第二个元素”是这样x,程序就可以正常工作。至于实际访问第二个元素,最有趣的方法是在中添加第三个参数(out参数)b,该参数x在基本情况和H递归情况下返回;这意味着我们可以从第二个递归调用的输出B中获取尾部的头部,当然,尾部的头部是列表的第二个元素。所以b看起来现在这个样子:

b(x,0,x).
b(T/H,N,H):-N#=A+B+1,b(H,A,_),b(T,B,J),H@>=J.

基本情况非​​常简单(空列表,返回计数为0,空列表的“第一个元素”为x)。递归情况的开始方式与以前相同(只是使用T/H符号而不是[H|T],并H作为额外的out参数);我们从头部的递归调用中忽略了多余的参数,但将其存储在J尾部的递归调用中。然后,我们要做的就是确保H大于或等于J(即“如果列表中至少有两个元素,则第一个大于或等于第二个元素),以确保列表最终排序。

不幸的是,setof如果我们尝试将的先前定义c与的这个新定义一起使用,将是一个合适的选择b,因为它对待未使用的out参数的方式与SQL差不多GROUP BY,这完全不是我们想要的。可以对其进行重新配置以执行我们想要的操作,但是重新配置会花费很多字符。相反,我们使用findall,它具有更方便的默认行为,并且仅长了两个字符,从而为我们提供了以下定义c

c(X,Y):-findall(A,b(A,X,_),L),length(L,Y).

这就是完整的程序;首先生成气泡模式,然后花费整个字节数对其进行计数(我们需要相当长的时间findall才能将生成器转换为列表,然后不幸地冗长地命名length以检查该列表的长度,并为函数声明添加了样板)。


“ Prolog实际上没有map谓词”:Prolog确实具有maplist/2-8谓词,尽管我不确定这是否会使这里的内容简短。
Fatalize

@Fatalize:呵呵,看起来是在较新版本中添加的。它不在我安装的文档中,并且在实践中不起作用:| ?- maplist(reverse,[A,B]). uncaught exception: error(existence_error(procedure,maplist/2),top_level/0)

真是奇怪 maplist是主要Prolog发行版​​本中提供的非常常用的谓词(例如SWI-Prolog和SiCStus)
Fatalize

10

Mathematica,68个字节

我敢打赌,可以从头开始实现(甚至在Mathematica中也可以),但这是内置版本:

<<NumericalDifferentialEquationAnalysis`
Last@ButcherTreeCount[#+1]&

ButcherTreeCount为0索引,因此为[#+1],并返回直至其参数为的所有值的列表Last@。但除此之外,它只是此函数的内置函数。但是,这需要加载一个包,这是第一行要做的。


8
“ Mathematica当然具有内置功能。”
orlp
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.