Ricostruire una permutazione


16

introduzione

Supponi di ricevere una permutazione casuale di noggetti. La permutazione è sigillata in una scatola, quindi non hai idea di quale delle n!possibili sia. Se sei riuscito ad applicare la permutazione a noggetti distinti, potresti immediatamente dedurne l'identità. Tuttavia, puoi applicare la permutazione solo ai nvettori binari di lunghezza , il che significa che dovrai applicarla più volte per riconoscerla. Chiaramente, applicandolo ai nvettori con solo uno 1fa il lavoro, ma se sei intelligente, puoi farlo con le log(n)applicazioni. Il codice per quel metodo sarà più lungo, sebbene ...

Questa è una sfida sperimentale in cui il tuo punteggio è una combinazione di lunghezza del codice e complessità della query , ovvero il numero di chiamate a una procedura ausiliaria. Le specifiche sono un po 'lunghe, quindi abbi pazienza.

L'obiettivo

Il tuo compito è scrivere una funzione denominata (o l'equivalente più vicino) f che accetta come input un numero intero positivo ne una permutazione pdei primi nnumeri interi, utilizzando l'indicizzazione basata su 0 o 1. Il suo output è la permutazione p. Tuttavia, non ti è consentito accedere pdirettamente alla permutazione . L'unica cosa che puoi fare con esso è applicarlo a qualsiasi vettore di nbit. A tale scopo, è necessario utilizzare una funzione ausiliaria Pche accetta una permutazione pe un vettore di bit ve restituisce il vettore permutato la cui p[i]coordinata th contiene il bit v[i]. Per esempio:

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

È possibile sostituire i "bit" con due valori distinti, come 3e -4, oe 'a'e 'b', e non è necessario correggerli, quindi è possibile chiamare Pcon entrambi [-4,3,3,-4]e [2,2,2,1]nella stessa chiamata a f. La definizione di Pnon viene conteggiata ai fini del punteggio.

punteggio

La complessità della query della soluzione su un determinato input è il numero di chiamate effettuate alla funzione ausiliaria P. Per rendere inequivocabile questa misura, la soluzione deve essere deterministica. Puoi usare numeri generati in modo pseudo-casuale, ma poi devi anche correggere un seme iniziale per il generatore.

In questo repository troverai un file chiamato permutations.txtche contiene 505 permutazioni, 5 di ogni lunghezza tra 50 e 150 inclusi, usando l'indicizzazione basata su 0 (incrementa ogni numero nel caso basato su 1). Ogni permutazione è sulla sua linea e i suoi numeri sono separati da spazi. Il tuo punteggio è il conteggio dei byte di f+ complessità media delle query su questi input . Il punteggio più basso vince.

Regole extra

Il codice con spiegazioni è preferito e le scappatoie standard sono vietate. In particolare, i singoli bit sono indistinguibili (quindi non è possibile assegnare un vettore di Integeroggetti Pe confrontare le loro identità) e la funzione Prestituisce sempre un nuovo vettore invece di riorganizzarne l'input. È possibile modificare liberamente i nomi di fe P, e l'ordine in cui si prendono i loro argomenti.

Se sei la prima persona a rispondere nel tuo linguaggio di programmazione, sei fortemente incoraggiato a includere un cablaggio di prova, inclusa un'implementazione della funzione Pche conti anche il numero di volte in cui è stata chiamata. Ad esempio, ecco il cablaggio per 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()

In alcune lingue, è impossibile scrivere una tale imbracatura. In particolare, Haskell non consente alla funzione pura Pdi registrare il numero di volte in cui viene chiamata. Per questo motivo, è possibile implementare nuovamente la soluzione in modo tale da calcolare anche la complessità della query e utilizzarla nel cablaggio.


Possiamo interpretare il "vettore di bit" come "vettore di due elementi distinti", per esempio, in questa definizione entrambi abaaabababaae -4 3 3 3 -4 3sarebbe un vettore di bit.
FUZxxl,

@FUZxxl Sì, purché i singoli articoli siano indistinguibili.
Zgarb,

Sono numeri nell'approccio all'implementazione che ho.
FUZxxl,

@FUZxxl Ho modificato le specifiche.
Zgarb,

Risposte:


11

J, 44.0693 22.0693 = 37 15 + 7.06931

Se non possiamo chiamare Psu i. n, possiamo almeno chiamata Psu ogni bit dei i. nseparatamente. Il numero di invocazioni di Pè >. 2 ^. n(⌈log 2 n ⌉). Credo che sia ottimale.

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

Ecco un'implementazione della funzione Pche utilizza il vettore di permutazione pe salva il numero di invocazioni Pinv.

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

Ecco un cablaggio di prova che riceve una permutazione e restituisce il numero di invocazioni di p:

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

Ed ecco come puoi usarlo nel file permutations.txt:

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

Spiegazione

La breve spiegazione è già fornita sopra, ma qui è una più dettagliata. Innanzitutto, fcon spazi inseriti e come funzione esplicita:

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

Leggere:

Sia f P in trasposizione sotto rappresentazione base-2 dei primi numeri interi y .

dove y è il parametro formale di f. in J, i parametri a una (funzione) sono chiamati x ed y se il verbo è diadica (ha due parametri) ed y se è monadic (ha un parametro).

Invece di invocare Psu i. n(cioè 0 1 2 ... (n - 1)), invochiamo Pogni posizione di bit dei numeri ini. n . Dal momento che tutte le permutazioni permutano allo stesso modo, possiamo riassemblare i bit permutati in numeri per ottenere un vettore di permutazione:

  • i. y- numeri interi da 0a y - 1.
  • #: y- yrappresentato nella base 2. Questo è esteso ai vettori di numeri in modo naturale. Ad esempio, #: i. 16produce:

    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- yinterpretato come un numero di base 2. In particolare, questo è l'inverso di #:; y ~: #. #:tiene sempre.

  • |: y- ytrasposto.
  • u&.v y- usotto v, ecco vinv u v ydove si vinvtrova l'inverso v. Si noti che |:è il suo contrario.

  • P y- la funzione Papplicata a ciascun vettore yper definizione.


3

Pyth 32 + 7.06931 = 37.06931

Ho trovato il seguente algoritmo completamente indipendente. Ma è più o meno la stessa cosa della soluzione J molto corta di FUZxxl (per quanto ho capito).

Innanzitutto la definizione della funzione P, che consente una matrice di bit secondo una permutazione sconosciuta.

D%GHJHVJ XJ@HN@GN)RJ

E poi il codice, che determina la permutazione.

Mmxmi_mbk2Cm%dHCm+_jk2*sltG]0GdG

Questo definisce una funzione g, che accetta due argomenti. Puoi chiamarlo da g5[4 2 1 3 0. Ecco una dimostrazione online . Non ho mai usato così tante (5) mappe nidificate.

A proposito, non ho ancora realizzato un'imbracatura di prova. Né la funzione Pconta quante volte la chiamo. Ho già passato molto tempo a capire l'algoritmo. Ma se leggi la mia spiegazione, è abbastanza ovvio che usa int(log2(n-1)) + 1chiamate ( = ceil(log2(n))). E sum(int(log2(n-1)) + 1 for n in range(50, 151)) / 101.0 = 7.069306930693069.

Spiegazione:

In realtà ho avuto difficoltà a trovare questo algoritmo. Non mi è stato affatto chiaro come raggiungere log(n). Quindi ho iniziato a fare alcuni esperimenti con piccoli n.

Prima nota: un array di bit raccoglie le stesse informazioni dell'array di bit del complemento. Pertanto tutti gli array di bit nella mia soluzione hanno al massimon/2 bit attivi.

n = 3:

Poiché possiamo utilizzare solo bit-array con 1 bit attivo, la soluzione ottimale dipende da due chiamate. Ad esempio, P([1, 0, 0])e P([0, 1, 0]). I risultati ci dicono il primo e il secondo numero della permutazione, indirettamente otteniamo il terzo.

n = 4:

Qui è un po 'interessante. Ora possiamo usare due tipi di array di bit. Quelli con 1 bit attivo e quelli con 2 bit attivi. Se utilizziamo un array di bit con un bit attivo, raccogliamo solo informazioni su un numero della permutazione e torniamo a n = 3, provocando 1 + 2 = 3chiamate di P. La parte interessante è che possiamo fare la stessa cosa con solo 2 chiamate, se utilizziamo array di bit con 2 bit attivi. Ad esempio, P([1, 1, 0, 0])eP([1, 0, 1, 0]) .

Diciamo che otteniamo gli output [1, 0, 0, 1]e [0, 0, 1, 1]. Vediamo che il bit numero 4 è attivo in entrambi gli array di output. L'unico bit che era attivo in entrambi gli array di input era il bit numero 1, quindi chiaramente inizia la permutazione 4. Ora è facile vedere che il bit 2 è stato spostato sul bit 1 (prima uscita) e il bit 3 è stato spostato sul bit 3 (seconda uscita). Pertanto la permutazione deve essere [4, 1, 3, 2].

n = 7:

Ora qualcosa di più grande. Mostrerò Pimmediatamente le chiamate di . Sono la volta che mi è venuta in mente dopo aver pensato e sperimentato un po '. (Nota che questi non sono quelli che uso nel mio codice.)

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

Se nei primi due array di output (e non nel terzo) è attivo il bit 2, sappiamo che la permutazione sposta il bit 1 nel bit 2, poiché il bit uno è l'unico bit attivo nei primi due array di input.

L'importante è che (interpretando gli input come matrice) ciascuna delle colonne sia unica. Questo mi ha ricordato sui codici di Hamming , dove si realizza la stessa cosa. Prendono semplicemente i numeri da 1 a 7 e usano la loro rappresentazione bit come colonne. Userò i numeri da 0 a 6. Ora la bella parte, possiamo interpretare gli output (di nuovo le colonne) come numeri di nuovo. Questi ci dicono il risultato della permutazione applicata [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]

Quindi dobbiamo solo risalire ai numeri. Il bit 0 è finito nel bit 5, il bit 1 è finito nel bit 0, il bit 2 è finito nel bit 6, ... Quindi la permutazione era [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

E il codice per la funzione di permutazione:

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
Se lo sostituisci *sltQ]0con m0sltQ, potresti avere 6 mappe nidificate della stessa lunghezza.
isaacg,

In base alla sfida, è necessario assegnare il codice che risolve la sfida a una funzione idealmente denominata fsebbene siano consentiti altri nomi. Il compito conta per il tuo punteggio.
FUZxxl,

@FUZxxl ha aggiornato il mio codice. Ora definisce una funzione ginvece di leggere da STDIN.
Jakube,

2

Mathematica, 63 + 100 = 163

Sto usando permutazioni basate su uno, poiché è così che funziona l'indicizzazione in Mathematica.

Innanzitutto, l'imbracatura di prova. Questa è la funzione di query p(i nomi definiti dall'utente non devono essere maiuscoli in Mathematica):

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

E preparare l'input insieme al loop di test:

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]

E infine, la mia attuale presentazione che utilizza l'algoritmo ingenuo per ora:

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

O con rientro:

f = (
     v = 0 q;
     v[[#]] = 1;
     Position[q~p~v, 1][[1, 1]]
) & /@ Range@Length[q = #] &
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.