每日最佳高尔夫#6:掷出d20


17

关于系列

首先,您可以像对待其他任何代码高尔夫挑战赛一样对待它,并回答它而不必担心系列赛。但是,在所有挑战中都有排行榜。您可以在第一篇文章中找到排行榜以及有关该系列的更多信息。

尽管我在本系列中有很多想法,但未来的挑战还没有定下来。如果您有任何建议,请在相关的沙箱帖子上让我知道。

第6洞:滚动d20

桌面RPG中最常见的模具是二十面模具(二十面体,通常称为d20)。掷骰子是您的任务。但是,如果您只是返回1到20之间的随机数,那将是微不足道的。因此,您的任务是为给定的模具生成一个随机的网。

我们将使用以下网络:

在此处输入图片说明

这是一个三角形带,因此可以很容易地将其表示为整数列表。例如,如果得到输入:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

那将对应于以下骰子(有趣的事实:这是魔术师使用的网:聚会生命计数器/旋转骰子)。

在此处输入图片说明

但是,这不是唯一代表此死的网络。根据我们展开脸部的方式,有60种不同的蚊帐。这里还有两个:

[1, 8, 9, 10, 2, 3, 4, 5, 6, 7, 17, 18, 19, 11, 12, 13, 14, 15, 16, 20]
[10, 9, 18, 19, 11, 12, 3, 2, 1, 8, 7, 17, 16, 20, 13, 14, 4, 5, 6, 15]

或以图形方式显示(为简单起见,我没有旋转面部标签):

在此处输入图片说明 在此处输入图片说明

挑战

给定一个代表骰子的整数列表(如上所述)和一个integer N,分别输出N,对应于给定骰子的均匀随机d20网络。(也就是说,60个可能的网络中的每一个都应具有相同的生成概率。)

当然,由于PRNG的技术局限性,不可能实现完美的均匀性。为了评估您提交的文件的均匀性,以下操作将被视为产生完全均匀的分布:

  • 从PRNG(在任何范围内)获取一个数字,该数字被证明是(近似)统一的。
  • 通过取模或乘法(或一些其他将值均匀分配的运算)将较大的一组数字上的均匀分布映射到较小的一组上。较大的集合必须包含至少1024倍于较小集合的可能值。

给定这些假设,您的算法必须产生完全均匀的分布。

您的程序应该能够在不到一秒钟的时间内生成100个网(因此,请不要尝试生成随机的网,直到一个对应于上面给出的模具)。

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

输入和输出可以采用任何方便,明确,平坦的列表格式。您可能会假设d20的面值为不同的正整数,适合您的语言的自然整数类型。

这是代码高尔夫球,因此最短的提交(以字节为单位)获胜。当然,每位用户最短的提交时间也将进入该系列的整体排行榜。

样本输出

对于输入

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

没有特定顺序的60种可能的网(假设我没有记错的话)是:

[11, 10, 9, 18, 19, 20, 13, 12, 3, 2, 1, 8, 7, 17, 16, 15, 14, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
[8, 7, 17, 18, 9, 10, 2, 1, 5, 6, 15, 16, 20, 19, 11, 12, 3, 4, 14, 13]
[3, 12, 13, 14, 4, 5, 1, 2, 10, 11, 19, 20, 16, 15, 6, 7, 8, 9, 18, 17]
[3, 4, 5, 1, 2, 10, 11, 12, 13, 14, 15, 6, 7, 8, 9, 18, 19, 20, 16, 17]
[11, 19, 20, 13, 12, 3, 2, 10, 9, 18, 17, 16, 15, 14, 4, 5, 1, 8, 7, 6]
[4, 14, 15, 6, 5, 1, 2, 3, 12, 13, 20, 16, 17, 7, 8, 9, 10, 11, 19, 18]
[2, 10, 11, 12, 3, 4, 5, 1, 8, 9, 18, 19, 20, 13, 14, 15, 6, 7, 17, 16]
[4, 5, 1, 2, 3, 12, 13, 14, 15, 6, 7, 8, 9, 10, 11, 19, 20, 16, 17, 18]
[10, 2, 1, 8, 9, 18, 19, 11, 12, 3, 4, 5, 6, 7, 17, 16, 20, 13, 14, 15]
[3, 2, 10, 11, 12, 13, 14, 4, 5, 1, 8, 9, 18, 19, 20, 16, 15, 6, 7, 17]
[7, 8, 1, 5, 6, 15, 16, 17, 18, 9, 10, 2, 3, 4, 14, 13, 20, 19, 11, 12]
[13, 12, 11, 19, 20, 16, 15, 14, 4, 3, 2, 10, 9, 18, 17, 7, 6, 5, 1, 8]
[16, 15, 14, 13, 20, 19, 18, 17, 7, 6, 5, 4, 3, 12, 11, 10, 9, 8, 1, 2]
[15, 16, 17, 7, 6, 5, 4, 14, 13, 20, 19, 18, 9, 8, 1, 2, 3, 12, 11, 10]
[20, 13, 12, 11, 19, 18, 17, 16, 15, 14, 4, 3, 2, 10, 9, 8, 7, 6, 5, 1]
[5, 4, 14, 15, 6, 7, 8, 1, 2, 3, 12, 13, 20, 16, 17, 18, 9, 10, 11, 19]
[10, 11, 12, 3, 2, 1, 8, 9, 18, 19, 20, 13, 14, 4, 5, 6, 7, 17, 16, 15]
[4, 3, 12, 13, 14, 15, 6, 5, 1, 2, 10, 11, 19, 20, 16, 17, 7, 8, 9, 18]
[19, 20, 13, 12, 11, 10, 9, 18, 17, 16, 15, 14, 4, 3, 2, 1, 8, 7, 6, 5]
[1, 8, 9, 10, 2, 3, 4, 5, 6, 7, 17, 18, 19, 11, 12, 13, 14, 15, 16, 20]
[8, 1, 5, 6, 7, 17, 18, 9, 10, 2, 3, 4, 14, 15, 16, 20, 19, 11, 12, 13]
[18, 9, 8, 7, 17, 16, 20, 19, 11, 10, 2, 1, 5, 6, 15, 14, 13, 12, 3, 4]
[12, 3, 2, 10, 11, 19, 20, 13, 14, 4, 5, 1, 8, 9, 18, 17, 16, 15, 6, 7]
[2, 3, 4, 5, 1, 8, 9, 10, 11, 12, 13, 14, 15, 6, 7, 17, 18, 19, 20, 16]
[10, 9, 18, 19, 11, 12, 3, 2, 1, 8, 7, 17, 16, 20, 13, 14, 4, 5, 6, 15]
[9, 8, 7, 17, 18, 19, 11, 10, 2, 1, 5, 6, 15, 16, 20, 13, 12, 3, 4, 14]
[16, 17, 7, 6, 15, 14, 13, 20, 19, 18, 9, 8, 1, 5, 4, 3, 12, 11, 10, 2]
[17, 7, 6, 15, 16, 20, 19, 18, 9, 8, 1, 5, 4, 14, 13, 12, 11, 10, 2, 3]
[1, 5, 6, 7, 8, 9, 10, 2, 3, 4, 14, 15, 16, 17, 18, 19, 11, 12, 13, 20]
[9, 18, 19, 11, 10, 2, 1, 8, 7, 17, 16, 20, 13, 12, 3, 4, 5, 6, 15, 14]
[16, 20, 19, 18, 17, 7, 6, 15, 14, 13, 12, 11, 10, 9, 8, 1, 5, 4, 3, 2]
[5, 1, 2, 3, 4, 14, 15, 6, 7, 8, 9, 10, 11, 12, 13, 20, 16, 17, 18, 19]
[8, 9, 10, 2, 1, 5, 6, 7, 17, 18, 19, 11, 12, 3, 4, 14, 15, 16, 20, 13]
[13, 20, 16, 15, 14, 4, 3, 12, 11, 19, 18, 17, 7, 6, 5, 1, 2, 10, 9, 8]
[6, 15, 16, 17, 7, 8, 1, 5, 4, 14, 13, 20, 19, 18, 9, 10, 2, 3, 12, 11]
[6, 5, 4, 14, 15, 16, 17, 7, 8, 1, 2, 3, 12, 13, 20, 19, 18, 9, 10, 11]
[7, 6, 15, 16, 17, 18, 9, 8, 1, 5, 4, 14, 13, 20, 19, 11, 10, 2, 3, 12]
[19, 18, 17, 16, 20, 13, 12, 11, 10, 9, 8, 7, 6, 15, 14, 4, 3, 2, 1, 5]
[14, 15, 6, 5, 4, 3, 12, 13, 20, 16, 17, 7, 8, 1, 2, 10, 11, 19, 18, 9]
[17, 18, 9, 8, 7, 6, 15, 16, 20, 19, 11, 10, 2, 1, 5, 4, 14, 13, 12, 3]
[6, 7, 8, 1, 5, 4, 14, 15, 16, 17, 18, 9, 10, 2, 3, 12, 13, 20, 19, 11]
[14, 13, 20, 16, 15, 6, 5, 4, 3, 12, 11, 19, 18, 17, 7, 8, 1, 2, 10, 9]
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
[7, 17, 18, 9, 8, 1, 5, 6, 15, 16, 20, 19, 11, 10, 2, 3, 4, 14, 13, 12]
[15, 6, 5, 4, 14, 13, 20, 16, 17, 7, 8, 1, 2, 3, 12, 11, 19, 18, 9, 10]
[9, 10, 2, 1, 8, 7, 17, 18, 19, 11, 12, 3, 4, 5, 6, 15, 16, 20, 13, 14]
[2, 1, 8, 9, 10, 11, 12, 3, 4, 5, 6, 7, 17, 18, 19, 20, 13, 14, 15, 16]
[12, 13, 14, 4, 3, 2, 10, 11, 19, 20, 16, 15, 6, 5, 1, 8, 9, 18, 17, 7]
[17, 16, 20, 19, 18, 9, 8, 7, 6, 15, 14, 13, 12, 11, 10, 2, 1, 5, 4, 3]
[18, 17, 16, 20, 19, 11, 10, 9, 8, 7, 6, 15, 14, 13, 12, 3, 2, 1, 5, 4]
[18, 19, 11, 10, 9, 8, 7, 17, 16, 20, 13, 12, 3, 2, 1, 5, 6, 15, 14, 4]
[11, 12, 3, 2, 10, 9, 18, 19, 20, 13, 14, 4, 5, 1, 8, 7, 17, 16, 15, 6]
[15, 14, 13, 20, 16, 17, 7, 6, 5, 4, 3, 12, 11, 19, 18, 9, 8, 1, 2, 10]
[19, 11, 10, 9, 18, 17, 16, 20, 13, 12, 3, 2, 1, 8, 7, 6, 15, 14, 4, 5]
[12, 11, 19, 20, 13, 14, 4, 3, 2, 10, 9, 18, 17, 16, 15, 6, 5, 1, 8, 7]
[20, 16, 15, 14, 13, 12, 11, 19, 18, 17, 7, 6, 5, 4, 3, 2, 10, 9, 8, 1]
[13, 14, 4, 3, 12, 11, 19, 20, 16, 15, 6, 5, 1, 2, 10, 9, 18, 17, 7, 8]
[5, 6, 7, 8, 1, 2, 3, 4, 14, 15, 16, 17, 18, 9, 10, 11, 12, 13, 20, 19]
[14, 4, 3, 12, 13, 20, 16, 15, 6, 5, 1, 2, 10, 11, 19, 18, 17, 7, 8, 9]

对于任何其他网络,只需将输入中ii第一个数字替换为第一个数字(其中i从1开始)。

相关挑战

排行榜

该系列的第一篇文章将产生一个排行榜。

为确保您的答案显示出来,请使用以下Markdown模板以标题开头每个答案:

## Language Name, N bytes

N您提交的文件大小在哪里。如果您提高了分数,则可以通过打败旧分数保持标题。例如:

## Ruby, <s>104</s> <s>101</s> 96 bytes

(目前未显示该语言,但是该代码段确实需要并对其进行解析,并且将来我可能会添加一个语言排行榜。)


关于我们的讨论,我添加了“必须”一词以使其更清楚。我认为这弥补了我一直在利用的漏洞。不过,我认为我使用的方法(虽然无效)很有趣。
水平河圣

这几乎就像我的沙盒帖子一样!
Rɪᴋᴇʀ

@RikerW当您将其沙箱化时,这就是我的想法。;)(当时,我的位置就在您的正下方。我认为这启发了您。)尽管您的位置显然要简单得多(这可能是一件好事)。
马丁·恩德

没见过你的。太奇怪了,我以为我读了第一页上的所有内容。但是不,我是独立制造的。
Rɪᴋᴇʀ

那不应该标题为“展开d20”吗?
泰特斯

Answers:


7

Ruby,160个字节(修订版B)

感谢MartinBüttner的建议,节省了17个字节。

->a,n{n.times{k=rand 60
%w{ABCD@GHIJKLMNEFPQRSO PFENOSRQHG@DCMLKJIAB GFPQHIA@DENOSRJKBCML}.map{|b|k.times{a=b.chars.map{|i|a[i.ord-64]}}}
k>29&&a.reverse!
p a}}

Ruby,177个字节(修订版A)

->n,a{n.times{k=rand(60)
h=->b{k.times{|j|a=(0..19).map{|i|a[b[i].ord-64]}}}
h['ABCD@GHIJKLMNEFPQRSO']
h['PFENOSRQHG@DCMLKJIAB']
h['GFPQHIA@DENOSRJKBCML']
k>29&&a.reverse!
p a}}

说明

通过围绕一个面(3倍),一个顶点(5倍)和两个边缘(2倍)旋转可以生成所有可能的方向。但不仅是任何面孔和边缘。轴必须具有正确的关系,并且旋转必须以正确的顺序进行,否则可能会发生奇怪的事情。

这就是我的做法(尽管我了解Martin的做法有所不同。)

四面体的所有方向可以通过以下三个对称操作的组合来生成:

a)绕着两个2折轴在边缘的中点右旋转(它们彼此成直角。如果将它们相乘,我们会发现通过剩下的一对边缘的第三个2折轴。)

b)绕3折轴旋转(与2折轴成对角线),穿过一个顶点和一个面。

二十面体的对称性是四面体的对称性的超集。在下图https://en.wikipedia.org/wiki/Icosahedron中的图像中,黄色的面孔和红色的面孔定义了两个不同的四面体(或一个八面体),而两个蓝色的面孔共有的边成三对直角(并位于立方体的表面上。)

二十面体的所有方向都可以通过上述对称操作加上另外的5倍操作生成。

在此处输入图片说明

上面提到的四个轴中的三个围绕三个旋转由''标记之间的魔力线表示。围绕第二个2倍轴的旋转是不同的,仅通过反转数组即可完成a[]

取消测试程序中的:

f=->a,n{
  n.times{                     #Iterate n times, using the result from the previous iteration to generate the next
    k=rand(60)                 #pick a random number

    h=->b{                     #helper function taking a string representing a transformation
      k.times{|j|              #which is performed on a using the number of times according to k
        a=(0..19).map{|i|a[b[i].ord-64]}
      }
    }

    #Rotate about axes k times (one 5-fold, one 3-fold, two 2-fold)
    #The first three axes have coprime rotation orders
    #And the rotations themselves take care of the modulus operation so no need to add it.
    #The second 2-fold rotation is equivalent to reversing the order
    #And is applied to the last 30 numbers as it is not coprime with the first 2-fold rotation.

    h['ABCD@GHIJKLMNEFPQRSO']  #rotate k times about 5-fold axis
    h['PFENOSRQHG@DCMLKJIAB']  #rotate k times about 3-fold axis
    h['GFPQHIA@DENOSRJKBCML']  #rotate k times about 2-fold axis
    k>29&&a.reverse!
    p a
  }
}

z=(1..20).map{|i|i} 
f[z,50]

替代解决方案131字节(由于二进制随机游走方法而无效,仅给出近似正确的分布。)

->a,n{(n*99).times{|i|s=['@DEFGHIABCMNOPQRJKLS','ABCD@GHIJKLMNEFPQRSO'][rand(2)] 
a=(0..19).map{|i|a[s[i].ord-64]}
i%99==98&&p(a)}}

这是一个打乱(很像用来打乱魔方的程序。)

我使用的具体轮播是最明显的两种:

-旋转120度(根据第一个图,旋转约1和20)

-旋转72度(根据第一个图,大约与1,2,3,4,5和16,17,18,19,20共有的顶点)。

我们将硬币翻转99次,然后每次执行两次旋转之一,具体取决于它是正面还是反面。

请注意,确定性地交替使用这些序列会导致序列很短。例如,使用正确的旋转感测,可以以仅为2的周期产生180度旋转。


似乎抛硬币来选择操作会产生比均匀分布更接近二项式分布的东西。
Sparr 2015年

如果状态空间大于随机游走,则为@Sparr。但是在这种情况下,仅6个步骤的随机游走可能会打开多达2 ^ 6 = 64个可能性(我没有计算过它们),并且我们的状态空间只有60个。经过99个步骤(2 ^ 99个不同的路径)一切都应至少与用于生成数字的PRNG的单个样本一样均匀地分布。
水平河圣

@MartinBüttner感谢您的提示,我已经应用了有效的提示。b.map不能直接工作,我需要b.chars.map使其工作(顺便说一句,因为我有Ruby 1.9.3,但在我的机器上不工作,但它确实可以在Ideone上工作。)这是一个合理的节省。我不认为更改不可打印字符的魔术字符串来保存-64遗嘱是可行的:%w{}\n(以及空格)解释为所生成数组中字符串之间的分隔符。我不知道它将如何处理其他不可打印的ASCII字符。
水平河圣

@Martin这比我预期的要难-最初,当我的代码无法正常工作时,我感到困惑,然后我休息了一下,突然意识到2折和3折对称必须与相同的相互关系在四面体上(我不得不将旋转的三角面更改为另一个三角面。)
Level River St

1
恭喜您是第一个拥有新解锁的几何图形徽章的用户。:)
马丁·恩德

2

IA-32机器代码,118字节

十六进制转储:

60 33 c0 51 8b 74 24 28 8b fa 6a 05 59 f3 a5 e8
21 00 00 00 20 c4 61 cd 6a 33 00 84 80 ad a8 33
32 00 46 20 44 8e 48 61 2d 2c 33 32 4a 00 21 20
a7 a2 90 8c 00 5b b1 04 51 0f c7 f1 83 e1 1f 49
7e f7 51 8b f2 56 8d 7c 24 e0 b1 14 f3 a4 5f 8b
f3 ac 8b ee d4 20 86 cc e3 0a 56 8d 74 04 e0 f3
a4 5e eb ed 59 e2 db 8b dd 59 e2 cc 59 83 c2 14
e2 91 61 c2 04 00

它有点长,所以先解释一下。我使用了与现有算法几乎相同的算法 Level River St答案。为了使答案有所不同,我采用了其他基本排列,这些排列不一定具有直观的几何意义,但是以某种方式更加方便。

该代码基本上必须生成一个排列,这是以下内容的顺序应用:

  1. 我称的3阶置换p3适用0 ... 2次
  2. 2的排列,我称之为q2,适用0或1次
  3. 5的排列,我称之为p5,应用了0 ... 4次
  4. 2的另一个排列,我称之为p2,应用了0或1次

这些排列有许多可能的选择。其中之一如下:

p3 = [0   4   5   6   7   8   9   1   2   3  13  14  15  16  17  18  10  11  12  19]
q2 = [4   5   6   7   0   1   2   3  13  14  15  16  17   8   9  10  11  12  19  18]
p5 = [6   7   0   4   5  14  15  16  17   8   9   1   2   3  13  12  19  18  10  11]
p2 = [1   0   7   8   9  10  11   2   3   4   5   6  16  17  18  19  12  13  14  15]

此选择比其他方法更好,因为此处的排列具有较长的顺序索引,可以通过游程长度编码将其压缩-4个排列仅29字节。

为了简化随机数的生成,我为所有函数选择了1 ... 30范围内的幂(每个置换应用了多少次)。这就导致了代码中的大量额外工作,因为例如p3每次将其自身乘以3倍时,就会变成一个身份置换。但是,这种方式的代码较小,并且只要范围可被30整除,输出将具有均匀分布。

同样,功率应为正,因此游程长度解码操作至少执行一次。

该代码具有4个嵌套循环。轮廓是这样的:

void doit(int n, uint8_t* output, const uint8_t input[20])
{    
    uint8_t temp[20];

    n_loop: for i_n = 0 ... n
    {
        memcpy(output, input, 20);
        expr_loop: for i_expr = 0 ... 3
        {
            power = rand(1 ... 30);
            power_loop: for i_power = 0 ... power
            {
                memcpy(temp, output, 20);
                output_index = 0;
                perm_loop: do while length > 0
                {
                    index = ...; // decode it
                    length = ...; // decode it
                    memcpy(output + output_index, temp + index, length);
                    output_index += length;
                }
            }
        }
        output += 20;
    }
}

我希望此伪代码比下面的内联汇编代码更清晰。

_declspec(naked) void __fastcall doit(int n, uint8_t* output, const uint8_t* input)
{
    _asm {
        pushad
        xor eax, eax

        n_loop:
            push ecx

            ; copy from input to output
            mov esi, [esp + 0x28]
            mov edi, edx
            push 5
            pop ecx
            rep movsd

            call end_of_data
#define rl(index, length) _emit(length * 32 + index)
            rl(0, 1)
            rl(4, 6)
            rl(1, 3)
            rl(13, 6)
            rl(10, 3)
            rl(19, 1)
            _emit(0)

            rl(4, 4)
            rl(0, 4)
            rl(13, 5)
            rl(8, 5)
            rl(19, 1)
            rl(18, 1)
            _emit(0)

            rl(6, 2)
            rl(0, 1)
            rl(4, 2)
            rl(14, 4)
            rl(8, 2)
            rl(1, 3)
            rl(13, 1)
            rl(12, 1)
            rl(19, 1)
            rl(18, 1)
            rl(10, 2)
            _emit(0)

            rl(1, 1)
            rl(0, 1)
            rl(7, 5)
            rl(2, 5)
            rl(16, 4)
            rl(12, 4)
            _emit(0)

            end_of_data:
            pop ebx ; load the address of the encoded data
            mov cl, 4

            expr_loop:
                push ecx

                make_rand:
                rdrand ecx
                and ecx, 31
                dec ecx
                jle make_rand

                ; input: ebx => encoding of permutation
                ; output: ebp => encoding of next permutation
                power_loop:
                    push ecx

                    ; copy from output to temp
                    mov esi, edx
                    push esi
                    lea edi, [esp - 0x20]
                    mov cl, 20
                    rep movsb
                    pop edi

                    ; ebx => encoding of permutation
                    ; edi => output
                    mov esi, ebx
                    perm_loop:
                        ; read a run-length
                        lodsb
                        mov ebp, esi

                        _emit(0xd4)             ; divide by 32, that is, split into
                        _emit(32)               ; index (al) and length (ah)
                        xchg cl, ah             ; set ecx = length; also makes eax = al
                        jecxz perm_loop_done    ; zero length => done decoding
                        push esi
                        lea esi, [esp + eax - 0x20]
                        rep movsb
                        pop esi
                        jmp perm_loop

                    perm_loop_done:
                    pop ecx
                    loop power_loop

                mov ebx, ebp
                pop ecx
                loop expr_loop

            pop ecx
            add edx, 20
            loop n_loop

        popad
        ret 4
    }
}

一些有趣的实现细节:

  • 我使用缩进式汇编,就像在高级语言中一样;否则代码将是一团糟
  • 我使用call和随后pop访问存储在代码中的数据(编码排列)
  • jecxz指令方便地让我使用零字节作为游程长度解码过程的终止
  • 幸运的是,数字30(2 * 3 * 5)几乎是2的幂。这使我可以使用短代码生成1 ... 30范围内的数字:

            and ecx, 31
            dec ecx
            jle make_rand
    
  • 我使用“通用除法”指令(aam)将字节分成位字段(3位长度和5位索引);幸运的是,在代码中的那个位置cl = 0,所以我从xchg

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.