순열 재구성


16

소개

n개체 의 임의 순열을 수행한다고 가정 합니다. 순열은 상자 안에 봉인되어 있으므로 n!가능한 값이 무엇인지 모릅니다 . 순열을 n개별 객체 에 적용한 경우 즉시 해당 ID를 추론 할 수 있습니다. 그러나 순열을 길이 n이진 벡터 에만 적용 할 수 있습니다. 즉,이를 인식하기 위해 순열을 여러 번 적용해야합니다. 분명히 n하나만 벡터에 적용하면 효과가 1있지만 영리한 경우 log(n)응용 프로그램으로 처리 할 수 있습니다 . 그 방법에 대한 코드는 더 길어질 것입니다 ...

이는 점수가 코드 길이와 쿼리 복잡성 의 조합 인 실험적인 문제 이며 보조 프로 시저에 대한 호출 수를 의미합니다. 사양이 약간 길기 때문에 나와 함께하십시오.

작업

당신의 임무는 0 기반 또는 1 기반 색인을 사용하여 양의 정수와 첫 번째 정수 의 순열 을 입력으로 취하는 명명 된 함수 (또는 가장 가까운 등가물) f 를 작성하는 것입니다. 출력은 순열 입니다. 그러나 순열에 직접 액세스 할 수 없습니다 . 당신이 할 수있는 유일한 것은 비트 벡터에 적용하는 것 입니다. 이를 위해 순열 과 비트 벡터를 받는 보조 함수 를 사용해야하며 , 좌표에 비트가 포함 된 순열 벡터를 반환합니다 . 예를 들면 다음과 같습니다.npnppnPpvp[i]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.txt0 기반 색인 (1 기반의 경우 각 숫자를 증가)를 사용하여 505 순열, 50, 150까지의 각 길이의 5를 포함합니다. 각 순열은 자체 행에 있으며 숫자는 공백으로 구분됩니다. 점수는 이러한 입력에서 + 평균 쿼리 복잡성 의 바이트 수입니다f . 최저 점수가 이깁니다.

추가 규칙

설명이 포함 된 코드가 선호되며 표준 허점은 허용되지 않습니다. 특히, 개별 비트는 구별 할 수 없으므로 ( Integer물체 벡터를 제공하고 P해당 ID를 비교할 수 없음)이 함수 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은 pure 함수 P가 호출 된 횟수를 기록 할 수 없습니다 . 이러한 이유로, 쿼리 복잡도를 계산하고이를 하네스에서 사용하는 방식으로 솔루션을 다시 구현할 수 있습니다.


우리는이 정의에 따라, 예를 들어, "두 가지 항목의 벡터"로 "비트 벡터"를 해석 할 수있는 모두 abaaabababaa-4 3 3 3 -4 3비트의 벡터가 될 것입니다.
FUZxxl

@FUZxxl 예, 개별 품목을 구분할 수없는 한 가능합니다.
Zgarb

그것들은 내가 가진 구현 방식의 숫자입니다.
FUZxxl

@ FUZxxl 사양을 편집했습니다.
Zgarb

답변:


11

J, 44.0693 22.0693 = 37 15 + 7.06931

에 전화 P를 걸 수 없으면 i. n적어도 P각 비트를 i. n별도로 호출 할 수 있습니다 . 호출 횟수 P>. 2 ^. n(⌈log 2 n ⌉)입니다. 나는 이것이 최적이라고 믿는다.

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를 제 2의베이스 표현이 아래의 전위하에 P (Y)의 정수.

여기서 yf에 대한 공식 매개 변수 입니다. J에서 (함수)에 대한 매개 변수 는 동사가 2 진이면 (2 개의 매개 변수가있는 경우) xy 라고 하고, 모나드이면 (하나의 매개 변수가있는 경우 ) y라고 합니다.

대신 호출하는 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 yu아래 v에서 그 반대의 vinv u v y위치 vinv입니다 v. 공지 |:자신의 역이다.

  • P y– 정의 Py의해 각 벡터에 적용되는 함수 .


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. 여기 온라인 데모가 있습니다 . 중첩 된지도를 너무 많이 사용하지 마십시오.

Btw 실제로 테스트 하네스를 만들지 않았습니다. 이 함수는 호출 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이 첫 번째 두 입력 배열에서 활성화 된 유일한 비트이므로 순열이 비트 1에서 비트 2로 이동한다는 것을 알 수 있습니다.

중요한 것은 (입력을 행렬로 해석) 각 열이 고유하다는 것입니다. 이것은 동일한 작업이 수행되는 Hamming 코드를 기억합니다 . 그들은 단순히 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 2

챌린지에 따라 챌린지를 해결하는 코드를 f다른 이름이 허용되지만 이상적으로 명명 된 함수에 할당해야합니다 . 과제는 점수로 계산됩니다.
FUZxxl

@FUZxxl이 내 코드를 업데이트했습니다. 이제 gSTDIN에서 읽는 대신 함수 를 정의합니다 .
Jakube

2

수학, 63 + 100 = 163

Mathematica에서 인덱싱이 작동하는 방식이기 때문에 1 기반 순열을 사용하고 있습니다.

먼저, 테스트 하니스. 이것은 쿼리 함수입니다 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 = #] &
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.