Tái thiết một Hoán vị


16

Giới thiệu

Giả sử rằng bạn được trao một hoán vị ngẫu nhiên của ncác đối tượng. Hoán vị được niêm phong trong một hộp, vì vậy bạn không biết đó là n!cái nào có thể. Nếu bạn quản lý để áp dụng hoán vị cho ncác đối tượng riêng biệt, bạn có thể suy ra ngay lập tức danh tính của nó. Tuy nhiên, bạn chỉ được phép áp dụng hoán vị cho ncác vectơ nhị phân có độ dài, có nghĩa là bạn sẽ phải áp dụng nó nhiều lần để nhận ra nó. Rõ ràng, áp dụng nó cho các nvectơ chỉ với một 1công việc, nhưng nếu bạn thông minh, bạn có thể làm điều đó với log(n)các ứng dụng. Mã cho phương thức đó sẽ dài hơn, mặc dù ...

Đây là một thử thách thử nghiệm trong đó điểm của bạn là sự kết hợp giữa độ dài mã và độ phức tạp truy vấn , nghĩa là số lượng cuộc gọi đến một thủ tục phụ trợ. Thông số kỹ thuật hơi dài, vì vậy hãy chịu đựng tôi.

Nhiệm vụ

Nhiệm vụ của bạn là viết một hàm được đặt tên (hoặc tương đương gần nhất) f , lấy đầu vào là một số nguyên dương nvà hoán vị pcủa các nsố nguyên đầu tiên , sử dụng lập chỉ mục dựa trên 0 hoặc dựa trên 1. Đầu ra của nó là hoán vị p. Tuy nhiên, bạn không được phép truy cập hoán vị ptrực tiếp . Điều duy nhất bạn có thể làm với nó là áp dụng nó cho bất kỳ vectơ nbit nào. Với mục đích này, bạn sẽ sử dụng một hàm phụ trợ Pcó hoán vị pvà vectơ bit vvà trả về vectơ hoán vị có p[i]tọa độ th chứa bit v[i]. Ví dụ:

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

Bạn có thể thay thế "bit" với bất kỳ hai giá trị khác biệt, chẳng hạn như 3-4, hoặc 'a''b', và họ không cần phải được cố định, vì vậy bạn có thể gọi Pvới cả hai [-4,3,3,-4][2,2,2,1]trong cuộc gọi cùng f. Định nghĩa của Pkhông được tính vào điểm số của bạn.

Chấm điểm

Độ phức tạp truy vấn của giải pháp của bạn trên một đầu vào đã cho là số lượng cuộc gọi mà nó thực hiện cho chức năng phụ trợ P. Để làm cho biện pháp này rõ ràng, giải pháp của bạn phải có tính quyết định. Bạn có thể sử dụng các số giả ngẫu nhiên được tạo, nhưng sau đó bạn cũng phải sửa một hạt giống ban đầu cho trình tạo.

Trong kho lưu trữ này, bạn sẽ tìm thấy một tệp có tên là permutations.txt50 hoán vị, 5 mỗi chiều dài từ 50 đến 150, bao gồm sử dụng lập chỉ mục dựa trên 0 (tăng mỗi số trong trường hợp dựa trên 1). Mỗi hoán vị nằm trên một dòng riêng và các số của nó được phân tách bằng dấu cách. Điểm của bạn là số byte của f+ độ phức tạp truy vấn trung bình trên các đầu vào này . Điểm số thấp nhất chiến thắng.

Quy tắc bổ sung

Mã với giải thích được ưa thích, và sơ hở tiêu chuẩn không được phép. Cụ thể, các bit riêng lẻ không thể phân biệt được (vì vậy bạn không thể đưa ra một vectơ Integerđối tượng Pvà so sánh danh tính của chúng) và hàm Pluôn trả về một vectơ mới thay vì sắp xếp lại đầu vào của nó. Bạn có thể tự do thay đổi tên của fP, và thứ tự mà họ lấy lý lẽ của họ.

Nếu bạn là người đầu tiên trả lời bằng ngôn ngữ lập trình của mình, bạn được khuyến khích đưa vào khai thác kiểm tra, bao gồm cả việc thực hiện chức năng Pcũng đếm số lần nó được gọi. Ví dụ, đây là khai thác cho 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()

Trong một số ngôn ngữ, không thể viết một khai thác như vậy. Đáng chú ý nhất, Haskell không cho phép hàm thuần Pghi lại số lần nó được gọi. Vì lý do này, bạn được phép thực hiện lại giải pháp của mình theo cách nó cũng tính toán độ phức tạp truy vấn của nó và sử dụng giải pháp đó trong khai thác.


Chẳng hạn, chúng ta có thể giải thích vectơ bit của bit bit là vectơ của hai mặt hàng riêng biệt, ví dụ, theo định nghĩa này cả hai abaaabababaa-4 3 3 3 -4 3sẽ là một vectơ của bit.
FUZxxl

@FUZxxl Có, miễn là các mục riêng lẻ không thể phân biệt được.
Zgarb

Chúng là những con số trong cách tiếp cận thực hiện mà tôi có.
FUZxxl

@FUZxxl Tôi đã chỉnh sửa thông số kỹ thuật.
Zgarb

Câu trả lời:


11

J, 44.0693 22.0693 = 37 15 + 7.06931

Nếu chúng ta không thể gọi Ptrên i. n, chúng ta có thể ở cuộc gọi tối thiểu Ptrên mỗi bit i. nriêng rẽ. Số lượng các yêu cầu của P>. 2 ^. n(⌈log 2 n ⌉ ). Tôi tin rằng điều này là tối ưu.

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

Dưới đây là một triển khai của hàm Psử dụng vectơ hoán vị pvà lưu số lượng các lệnh vào Pinv.

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

Đây là một khai thác thử nghiệm nhận được một hoán vị và trả về số lượng các lệnh p:

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

Và đây là cách bạn có thể sử dụng nó trên tập tin permutations.txt:

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

Giải trình

Giải thích ngắn đã được cung cấp ở trên, nhưng đây là một chi tiết chi tiết hơn. Đầu tiên, fvới các khoảng trắng được chèn và như là một hàm rõ ràng:

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

Đọc:

Đặt f là P dưới sự hoán vị dưới biểu diễn cơ số 2 của các số nguyên y đầu tiên .

Trong đó y là tham số chính thức cho f. trong J, các tham số cho một (hàm) được gọi là xy nếu động từ là dyadic (có hai tham số) và y nếu nó là đơn âm (có một tham số).

Thay vì gọi Pvào i. n(ví dụ 0 1 2 ... (n - 1)), chúng tôi gọi Ptrên mỗi vị trí bit của các con số trong i. n. Vì tất cả các hoán vị hoán vị theo cùng một cách, chúng ta có thể ghép lại các bit được hoán vị thành số để có được một vectơ hoán vị:

  • i. y- số nguyên từ 0đến y - 1.
  • #: y- được ybiểu diễn trong cơ sở 2. Điều này được mở rộng thành các vectơ số theo cách tự nhiên. Chẳng hạn, #: i. 16sản lượng:

    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
    
  • #. y- được yhiểu là một số cơ sở 2. Đáng chú ý, đây là nghịch đảo #:; y ~: #. #:luôn luôn giữ

  • |: y- yhoán vị.
  • u&.v y- udưới v, đó là vinv u v ynơi vinvnghịch đảo v. Lưu ý rằng đó |:là nghịch đảo của chính nó.

  • P y- hàm Páp dụng cho mỗi vectơ ytheo định nghĩa.


3

Số 32 + 7.06931 = 37.06931

Tôi tìm thấy các thuật toán sau hoàn toàn độc lập. Nhưng nó ít nhiều giống với giải pháp J rất ngắn của FUZxxl (theo như tôi hiểu).

Đầu tiên định nghĩa của hàm P, hoán vị một mảng bit theo một hoán vị chưa biết.

D%GHJHVJ XJ@HN@GN)RJ

Và sau đó là mã, xác định hoán vị.

Mmxmi_mbk2Cm%dHCm+_jk2*sltG]0GdG

Điều này xác định một hàm g, có hai đối số. Bạn có thể gọi nó bằng cách g5[4 2 1 3 0. Đây là một cuộc biểu tình trực tuyến . Không bao giờ sử dụng rất nhiều (5) bản đồ lồng nhau.

Btw Tôi chưa thực sự làm một khai thác thử nghiệm. Hàm này cũng không Pđếm được bao nhiêu lần tôi gọi nó. Tôi đã dành nhiều thời gian để tìm ra thuật toán. Nhưng nếu bạn đọc lời giải thích của tôi, thì rõ ràng là nó sử dụng int(log2(n-1)) + 1các cuộc gọi ( = ceil(log2(n))). Và sum(int(log2(n-1)) + 1 for n in range(50, 151)) / 101.0 = 7.069306930693069.

Giải trình:

Tôi thực sự đã có một thời gian khá khó khăn để tìm ra thuật toán này. Nó không rõ ràng với tôi, làm thế nào để đạt được log(n). Vì vậy, tôi bắt đầu làm một số thí nghiệm với nhỏ n.

Lưu ý đầu tiên: Một mảng bit tập hợp thông tin giống như mảng bit bổ sung. Do đó, tất cả các mảng bit trong giải pháp của tôi có nhiều nhất là n/2bit hoạt động.

n = 3:

Vì chúng ta chỉ có thể sử dụng mảng bit với 1 bit hoạt động, nên giải pháp tối ưu phụ thuộc vào hai cuộc gọi. Ví dụ P([1, 0, 0])P([0, 1, 0]). Kết quả cho chúng ta biết số thứ nhất và thứ hai của hoán vị, gián tiếp chúng ta có được số thứ ba.

n = 4:

Ở đây nó có một chút thú vị. Bây giờ chúng ta có thể sử dụng hai loại mảng bit. Những người có 1 bit hoạt động và những người có 2 bit hoạt động. Nếu chúng ta sử dụng một mảng bit với một bit hoạt động, chúng ta chỉ thu thập thông tin về một số hoán vị và quay trở lại n = 3, dẫn đến 1 + 2 = 3các cuộc gọi của P. Phần thú vị là chúng ta có thể làm điều tương tự chỉ với 2 cuộc gọi, nếu chúng ta sử dụng mảng bit với 2 bit hoạt động. Ví dụ P([1, 1, 0, 0])P([1, 0, 1, 0]).

Hãy nói rằng chúng tôi nhận được kết quả đầu ra [1, 0, 0, 1][0, 0, 1, 1]. Chúng ta thấy rằng bit số 4 đang hoạt động trong cả hai mảng đầu ra. Bit duy nhất hoạt động trong cả hai mảng đầu vào là bit số 1, vì vậy rõ ràng hoán vị bắt đầu bằng 4. Bây giờ thật dễ dàng để thấy rằng bit 2 đã được chuyển sang bit 1 (đầu ra đầu tiên) và bit 3 đã được chuyển sang bit 3 (đầu ra thứ hai). Do đó hoán vị phải được [4, 1, 3, 2].

n = 7:

Bây giờ một cái gì đó lớn hơn. Tôi sẽ hiển thị các cuộc gọi của Pngay lập tức. Họ là người duy nhất, mà tôi nghĩ ra sau một chút suy nghĩ và thử nghiệm. (Lưu ý đây không phải là những cái mà tôi sử dụng trong mã của mình.)

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

Nếu trong hai mảng đầu ra đầu tiên (chứ không phải trong phần ba) thì bit 2 đang hoạt động, chúng ta biết rằng hoán vị di chuyển bit 1 sang bit 2, vì bit một là bit duy nhất hoạt động trong hai mảng đầu vào đầu tiên.

Điều quan trọng là, (diễn giải các đầu vào dưới dạng ma trận) mỗi cột là duy nhất. Điều này nhớ tôi về mã Hamming , nơi điều tương tự được thực hiện. Họ chỉ cần lấy các số từ 1 đến 7 và sử dụng biểu diễn bit của chúng làm cột. Tôi sẽ sử dụng các số từ 0 đến 6. Bây giờ là phần hay, chúng ta có thể diễn giải các đầu ra (một lần nữa là các cột) dưới dạng số một lần nữa. Chúng cho chúng ta biết kết quả của sự hoán vị được áp dụng cho [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]

Vì vậy, chúng tôi chỉ phải theo dõi lại những con số. Bit 0 kết thúc ở bit 5, bit 1 kết thúc ở bit 0, bit 2 kết thúc ở bit 6, ... Vậy là hoán vị là [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

Và mã cho hàm hoán vị:

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
Nếu bạn thay thế *sltQ]0bằng m0sltQ, bạn có thể có 6 bản đồ lồng nhau ở cùng độ dài.
isaacg

Bạn nên, theo thách thức, chỉ định mã của bạn để giải quyết thách thức cho một chức năng được đặt tên lý tưởng fmặc dù các tên khác được cho phép. Bài tập được tính vào điểm số của bạn.
FUZxxl

@FUZxxl đã cập nhật mã của tôi. Bây giờ định nghĩa một hàm gthay vì đọc từ STDIN.
Jakube

2

Toán học, 63 + 100 = 163

Tôi đang sử dụng hoán vị dựa trên một, vì đó là cách lập chỉ mục hoạt động trong Mathematica.

Đầu tiên, khai thác thử nghiệm. Đây là hàm truy vấn p(tên do người dùng xác định không nên viết hoa trong Mathicala):

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

Và chuẩn bị đầu vào cùng với vòng kiểm tra:

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]

Và cuối cùng, trình thực tế của tôi sử dụng thuật toán ngây thơ bây giờ:

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

Hoặc với vết lõm:

f = (
     v = 0 q;
     v[[#]] = 1;
     Position[q~p~v, 1][[1, 1]]
) & /@ Range@Length[q = #] &
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.