重构排列


16

介绍

假设您获得了n对象的随机排列。排列被密封在一个盒子中,因此您不知道n!它是哪种。如果设法将置换应用于n不同的对象,则可以立即推断出其身份。但是,只允许将置换应用于长度n二进制矢量,这意味着您必须多次应用置换才能识别它。显然,仅将其应用于n向量1就可以完成工作,但是如果您很聪明,则可以使用log(n)应用程序来完成。但是,该方法的代码将更长。

这是一项实验性挑战,您的分数是代码长度和查询复杂度的组合,这意味着对辅助过程的调用次数。规格有点长,请耐心等待。

任务

您的任务是编写一个命名函数(或最接近的对等函数) f,该函数使用基于0或基于1的索引作为输入n,并使用正整数和pn整数的排列作为输入。其输出是排列p。但是,不允许您p直接访问排列。您唯一可以做的就是将其应用于任何n位向量。为此,您应该使用一个辅助函数P,该函数接受一个置换p和一个bits向量v,并返回其p[i]th坐标包含bit 的置换向量v[i]。例如:

P([1,2,3,4,0], [1,1,0,0,0]) == [0,1,1,0,0]

您可以将“位”替换为任意两个不同的值,例如3-4,或'a''b',并且它们不需要固定,因此可以P[-4,3,3,-4][2,2,2,1]的同一调用中使用两者进行调用f。的定义P不会计入您的分数。

计分

解决方案在给定输入上的查询复杂度是它对辅助函数进行的调用次数P。为了使此度量明确,您的解决方案必须是确定性的。您可以使用伪随机生成的数字,但随后还必须为生成器确定初始种子。

此存储库中,您将找到一个名为的文件permutations.txt,该文件包含505个排列,使用从0开始的索引(在从1开始的情况下,增加每个数字),每个长度在5到50之间(含150和150)之间。每个排列都在其自己的行上,并且其编号由空格分隔。您的分数是这些输入的字节数f+平均查询复杂度。最低分获胜。

额外规则

最好使用带说明的代码,并且不允许出现标准漏洞。特别是,各个位是无法区分的(因此,您不能给Integer对象提供矢量P并比较它们的身份),并且该函数P始终返回一个新矢量,而不是重新安排其输入。您可以自由更改和的名称f以及P它们接受参数的顺序。

如果您是第一个使用您的编程语言回答的人,我们强烈建议您包括一个测试工具,其中包括该函数的实现,该实现P还计算调用它的次数。例如,这是Python 3的工具。

def f(n,p):
    pass # Your submission goes here

num_calls = 0

def P(permutation, bit_vector):
    global num_calls
    num_calls += 1
    permuted_vector = [0]*len(bit_vector)
    for i in range(len(bit_vector)):
        permuted_vector[permutation[i]] = bit_vector[i]
    return permuted_vector

num_lines = 0
file_stream = open("permutations.txt")
for line in file_stream:
    num_lines += 1
    perm = [int(n) for n in line.split()]
    guess = f(len(perm), perm)
    if guess != perm:
        print("Wrong output\n %s\n given for input\n %s"%(str(guess), str(perm)))
        break
else:
    print("Done. Average query complexity: %g"%(num_calls/num_lines,))
file_stream.close()

在某些语言中,不可能编写这样的工具。最值得注意的是,Haskell不允许纯函数P记录其被调用的次数。因此,允许您重新实现您的解决方案,使其还计算其查询复杂度,并将其用于工具中。


例如,在这个定义下,我们能否将“位向量”解释为“两个不同项的向量”,abaaabababaa并且-4 3 3 3 -4 3将是位向量。
FUZxxl 2015年

@FUZxxl是的,只要各个项目没有区别即可。
Zgarb 2015年

它们是我所采用的实施方法中的数字。
FUZxxl 2015年

@FUZxxl我编辑了规范。
Zgarb 2015年

Answers:


11

J,44.0693 22.0693 = 37 15 + 7.06931

如果我们不能叫Pi. n,我们至少可以调用P上的每一位i. n分开。的调用的数目P>. 2 ^. n(⌈log 2 Ñ ⌉)。我相信这是最佳选择。

f=:P&.|:&.#:@i.

这是P使用排列向量p并将调用次数保存到中的函数的实现Pinv

P =: 3 : 0"1
 Pinv =: Pinv + 1
 assert 3 > # ~. y    NB. make sure y is binary
 p { y
)

这是一个测试工具,它接收排列并返回的调用次数p

harness =: 3 : 0
 Pinv =: 0
 p =: y
 assert y = f # y     NB. make sure f computed the right permutation
 Pinv
)

这是如何在文件上使用它permutations.txt

NB. average P invocation count
(+/ % #) harness@".;._2 fread 'permutations.txt'

说明

上面已经提供了简短的说明,但是这里有更详细的说明。首先,f插入空格并作为显式函数:

f =: P&.|:&.#:@i.
f =: 3 : 'P&.|:&.#: i. y'

读:

f为在前y个整数的base-2表示下的换位下的P。

其中yf的形式参数以J,参数的(功能)被称为Xÿ如果动词是二元(有两个参数)和ÿ如果它是一元(具有一个参数)。

不是调用的Pi. n(即0 1 2 ... (n - 1)),我们调用P上的号码中的各个位的位置i. n。由于所有置换都以相同的方式置换,因此我们可以将置换后的位重新组合为数字,以获得置换向量:

  • i. y–从0到的整数y - 1
  • #: yy以基数2表示。以自然方式扩展到数字向量。例如,#: i. 16收益:

    0 0 0 0
    0 0 0 1
    0 0 1 0
    0 0 1 1
    0 1 0 0
    0 1 0 1
    0 1 1 0
    0 1 1 1
    1 0 0 0
    1 0 0 1
    1 0 1 0
    1 0 1 1
    1 1 0 0
    1 1 0 1
    1 1 1 0
    1 1 1 1
    
  • #. yy解释为以2为底的数字。值得注意的是,这与之相反#:y ~: #. #:总是成立。

  • |: yy转置。
  • u&.v y- uv,那就是vinv u v y这里vinv是逆v。注意这|:是它自己的逆。

  • P y–根据定义P应用于每个向量的函数y


3

Pyth 32 + 7.06931 = 37.06931

我发现以下算法完全独立。但这与FUZxxl非常简短的J解决方案差不多(就我所知)。

首先是函数的定义,该函数P根据未知排列对位数组进行排列。

D%GHJHVJ XJ@HN@GN)RJ

然后是确定排列的代码。

Mmxmi_mbk2Cm%dHCm+_jk2*sltG]0GdG

这定义了一个带有g两个参数的函数。您可以通过拨打电话g5[4 2 1 3 0。这是一个在线演示。从未使用过太多(5)嵌套地图。

顺便说一句,我实际上还没有进行测试。该函数也不P计算我调用它的次数。我已经花了很多时间解决算法。但是,如果您阅读了我的解释,很明显它使用了int(log2(n-1)) + 1调用(= ceil(log2(n)))。和sum(int(log2(n-1)) + 1 for n in range(50, 151)) / 101.0 = 7.069306930693069

说明:

我实际上很难找到这个算法。我根本不清楚如何实现log(n)。所以我开始做一些小实验n

首先说明:位数组收集与补码位数组相同的信息。因此,我的解决方案中的所有位阵列最多都具有n/2活动位。

n = 3:

由于我们只能使用具有1个活动位的位数组,因此最佳解决方案取决于两次调用。例如P([1, 0, 0])P([0, 1, 0])。结果告诉我们排列的第一个和第二个数,间接得到第三个。

n = 4:

在这里,它有点有趣。现在,我们可以使用两种位阵列。具有1个有效位的那些和具有2个有效位的那些。如果我们使用一个带有一个有效位的位数组,那么我们只会收集有关一个排列数的信息,然后回退到n = 3,从而1 + 2 = 3调用P。有趣的是,如果我们使用具有2个有效位的位数组,则仅用2个调用就可以完成相同的操作。例如P([1, 1, 0, 0])P([1, 0, 1, 0])

假设我们得到了输出[1, 0, 0, 1][0, 0, 1, 1]。我们看到,位号4在两个输出数组中都处于活动状态。在两个输入数组中唯一有效的位是位号1,因此显然置换从开始4。现在很容易看到,第2位被移至第1位(第一个输出),第3位被移至第3位(第二个输出)。因此,排列必须为[4, 1, 3, 2]

n = 7:

现在更大。我将P立即显示呼叫。经过一番思考和尝试后,我想到了它们。(请注意,这些不是我在代码中使用的。)

P([1, 1, 1, 0, 0, 0, 0])
P([1, 0, 0, 1, 1, 0, 0])
P([0, 0, 1, 1, 0, 1, 0])

如果在前两个输出数组中(而不是在第三个输出数组中)第2位有效,则我们知道置换将第1位移至第2位,因为第1个输入数组中唯一的位是bit 1。

重要的是,每列(将输入解释为矩阵)都是唯一的。这让我想起了汉明代码,完成了同样的事情。它们只是采用数字1到7,并将其位表示形式用作列。我将使用数字0到6。现在好了,我们可以再次将输出(再次在列中)解释为数字。这些告诉我们对进行置换的结果[0, 1, 2, 3, 4, 5, 6]

   0  1  2  3  4  5  6      1  3  6  4  5  0  2
P([0, 1, 0, 1, 0, 1, 0]) = [1, 1, 0, 0, 1, 0, 0]
P([0, 0, 1, 1, 0, 0, 1]) = [0, 1, 1, 0, 0, 0, 1]
P([0, 0, 0, 0, 1, 1, 1]) = [0, 0, 1, 1, 1, 0, 0]

因此,我们只需要追溯这些数字即可。位0结束于位5,位1结束于位0,位2结束于位6,...因此排列为[5, 0, 6, 1, 3, 4, 2]

Mmxmi_mbk2Cm%dHCm+_jk2*sltG]0GdG
M                                 define a function g(G, H), that will return
                                  the result of the following computation:
                                  G is n, and H is the permutation. 
                m            G     map each k in [0, 1, ..., Q-1] to:
                  _                   their inverse
                   jk2                binary representation (list of 1s and 0s)
                 +                    extended with 
                      *sltG]0         int(log2(Q - 1)) zeros
               C                   transpose matrix # rows that are longer 
                                                   # than others are shortened
           m%dH                    map each row (former column) d of 
                                   the matrix to the function P (here %)
          C                        transpose back
   m                              map each row k to:                         
    i    2                           the decimal number of the 
     _mbk                            inverse list(k) # C returns tuple :-(
Let's call the result X.  
 m                             G   map each d in [0, 1, ..., Q - 1] to:
  x         X                 d       the index of d in X

以及置换函数的代码:

D%GHJHVJ XJ@HN@GN)RJ
D%GH                     def %(G, H):  # the function is called %
    JH                     J = copy(H)
      VJ         )        for N in [0, 1, ..., len(J) - 1]: 
         XJ@HN@GN            J[H[N]] = G[N]           
                  RJ      return J

1
如果替换*sltQ]0m0sltQ,则可以有6个相同长度的嵌套地图。
isaacg

您应该根据挑战,将解决挑战的代码分配给理想命名的函数,f尽管允许使用其他名称。作业将计入您的分数。
FUZxxl 2015年

@FUZxxl更新了我的代码。现在定义一个函数,g而不是从STDIN读取。
2015年

2

Mathematica,63 + 100 = 163

我正在使用基于1的排列,因为这就是在Mathematica中进行索引的方式。

首先,测试工具。这是查询功能p(用户定义的名称在Mathematica中不应为大写):

p[perm_, vec_] := (
   i += 1;
   vec[[Ordering@perm]]
   );

以及输入准备以及测试循环:

permutations = 
  ToExpression@StringSplit@# + 1 & /@ 
   StringSplit[Import[
     "https://raw.githubusercontent.com/iatorm/permutations/master/permutations.txt"
   ], "\n"];
total = 0;
(
    i = 0;
    result = f@#;
    If[# != result, 
      Print["Wrong result for ", #, ". Returned ," result ", instead."]
    ];
    total += i;
    ) & /@ permutations;
N[total/Length@permutations]

最后,我现在使用天真的算法的实际提交内容:

f=(v=0q;v[[#]]=1;Position[q~p~v,1][[1,1]])&/@Range@Length[q=#]&

或缩进:

f = (
     v = 0 q;
     v[[#]] = 1;
     Position[q~p~v, 1][[1, 1]]
) & /@ Range@Length[q = #] &
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.