Reconstruire une permutation

16

introduction

Supposons que l'on vous remette une permutation aléatoire d' nobjets. La permutation est scellée dans une boîte, vous n'avez donc aucune idée de celle qui est n!possible. Si vous parveniez à appliquer la permutation à ndes objets distincts, vous pourriez immédiatement en déduire son identité. Cependant, vous n'êtes autorisé à appliquer la permutation qu'aux nvecteurs binaires de longueur , ce qui signifie que vous devrez l'appliquer plusieurs fois pour la reconnaître. Clairement, l'appliquer aux nvecteurs avec un seul 1fait le travail, mais si vous êtes intelligent, vous pouvez le faire avec des log(n)applications. Le code de cette méthode sera cependant plus long ...

Il s'agit d'un défi expérimental où votre score est une combinaison de longueur de code et de complexité de requête , c'est-à-dire le nombre d'appels à une procédure auxiliaire. La spécification est un peu longue, alors restez avec moi.

La tâche

Votre tâche consiste à écrire une fonction nommée (ou son équivalent le plus proche) f qui prend en entrée un entier positif net une permutation pdes premiers nentiers, en utilisant une indexation basée sur 0 ou 1. Sa sortie est la permutation p. Cependant, vous n'êtes pas autorisé à accéder pdirectement à la permutation . La seule chose que vous pouvez en faire est de l'appliquer à n'importe quel vecteur de nbits. À cet effet, vous devez utiliser une fonction auxiliaire Pqui prend une permutation pet un vecteur de bits v, et renvoie le vecteur permuté dont p[i]la coordonnée e contient le bit v[i]. Par exemple:

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

Vous pouvez remplacer les "bits" par deux valeurs distinctes, telles que 3et -4, ou 'a'et 'b', et elles n'ont pas besoin d'être fixées, vous pouvez donc appeler Pavec les deux [-4,3,3,-4]et [2,2,2,1]dans le même appel à f. La définition de Pn'est pas prise en compte dans votre score.

Notation

La complexité de la requête de votre solution sur une entrée donnée est le nombre d'appels qu'elle fait à la fonction auxiliaire P. Pour rendre cette mesure sans ambiguïté, votre solution doit être déterministe. Vous pouvez utiliser des nombres générés de manière pseudo-aléatoire, mais vous devez également corriger une graine initiale pour le générateur.

Dans ce référentiel, vous trouverez un fichier appelé permutations.txtqui contient 505 permutations, 5 de chaque longueur entre 50 et 150 inclus, en utilisant une indexation basée sur 0 (incrémentez chaque nombre dans le cas basé sur 1). Chaque permutation est sur sa propre ligne et ses nombres sont séparés par des espaces. Votre score est un nombre d'octets de f+ complexité de requête moyenne sur ces entrées . Le score le plus bas l'emporte.

Règles supplémentaires

Un code avec des explications est préférable et les failles standard ne sont pas autorisées. En particulier, les bits individuels sont indiscernables (vous ne pouvez donc pas donner un vecteur d' Integerobjets à Pet comparer leurs identités), et la fonction Prenvoie toujours un nouveau vecteur au lieu de réorganiser son entrée. Vous pouvez modifier librement les noms de fet P, et l'ordre dans lequel ils prennent leurs arguments.

Si vous êtes la première personne à répondre dans votre langage de programmation, vous êtes fortement encouragé à inclure un harnais de test, y compris une implémentation de la fonction Pqui compte également le nombre de fois où elle a été appelée. À titre d'exemple, voici le harnais pour 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()

Dans certaines langues, il est impossible d'écrire un tel harnais. Plus particulièrement, Haskell ne permet pas à la fonction pure Pd'enregistrer le nombre de fois où elle est appelée. Pour cette raison, vous êtes autorisé à réimplémenter votre solution de manière à ce qu'elle calcule également la complexité de sa requête et à l'utiliser dans le faisceau.

Zgarb
la source
Pouvons-nous interpréter «vecteur de bits» comme «vecteur de deux éléments distincts», par exemple, sous cette définition à la fois abaaabababaaet -4 3 3 3 -4 3serait un vecteur de bits.
FUZxxl
@FUZxxl Oui, tant que les éléments individuels sont indiscernables.
Zgarb
Ce sont des chiffres dans l'approche de mise en œuvre que j'ai.
FUZxxl
@FUZxxl J'ai édité la spécification.
Zgarb

Réponses:

11

J, 44,0693 22,0693 = 37 15 + 7,06931

Si nous ne pouvons pas appeler Psur i. n, nous pouvons au moins appel Psur chaque bit i. nséparément. Le nombre d' appels de Pest >. 2 ^. n(⌈log 2 n ⌉). Je pense que c'est optimal.

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

Voici une implémentation de la fonction Pqui utilise le vecteur de permutation pet enregistre le nombre d'appels dans Pinv.

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

Voici un harnais de test qui reçoit une permutation et renvoie le nombre d'appels de p:

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

Et voici comment vous pouvez l'utiliser sur le fichier permutations.txt:

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

Explication

La brève explication est déjà fournie ci-dessus, mais en voici une plus détaillée. Tout d'abord, favec des espaces insérés et comme fonction explicite:

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

Lis:

Soit f la transposition P sous représentation en base 2 des premiers y entiers.

y est le paramètre formel de f. en J, les paramètres d'une (fonction) sont appelés x et y si le verbe est dyadique (a deux paramètres) et y s'il est monadique (a un paramètre).

Au lieu d'invoquer Psur i. n(c. -à 0 1 2 ... (n - 1)), nous invoquons Psur chaque position binaire des nombres dans i. n. Puisque toutes les permutations permutent de la même manière, nous pouvons réassembler les bits permutés en nombres pour obtenir un vecteur de permutation:

  • i. y- entiers de 0à y - 1.
  • #: y- yreprésenté en base 2. Il est étendu aux vecteurs de nombres de manière naturelle. Par exemple, #: i. 16donne:

    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- yinterprété comme un nombre de base 2. C'est notamment l'inverse de #:; y ~: #. #:tient toujours.

  • |: y- ytransposé.
  • u&.v y- usous v, c'est là vinv u v yvinvest l'inverse de v. Notez que |:c'est son propre inverse.

  • P y- la fonction Pappliquée à chaque vecteur ypar définition.

FUZxxl
la source
3

Pyth 32 + 7,06931 = 37,06931

J'ai trouvé l'algorithme suivant complètement indépendant. Mais c'est plus ou moins la même chose que la solution J très courte de FUZxxl (si je comprends bien).

D'abord la définition de la fonction P, qui permute un tableau de bits selon une permutation inconnue.

D%GHJHVJ XJ@HN@GN)RJ

Et puis le code, qui détermine la permutation.

Mmxmi_mbk2Cm%dHCm+_jk2*sltG]0GdG

Ceci définit une fonction g, qui prend deux arguments. Vous pouvez l'appeler par g5[4 2 1 3 0. Voici une démonstration en ligne . Jamais utilisé autant de cartes imbriquées.

Btw, je n'ai pas fait de harnais de test. La fonction ne Pcompte pas non plus combien de fois je l'appelle. J'ai passé beaucoup de temps à trouver déjà l'algorithme. Mais si vous lisez mon explication, il est bien évident qu'elle utilise des int(log2(n-1)) + 1appels ( = ceil(log2(n))). Et sum(int(log2(n-1)) + 1 for n in range(50, 151)) / 101.0 = 7.069306930693069.

Explication:

En fait, j'ai eu beaucoup de mal à trouver cet algorithme. Je ne savais pas du tout comment y parvenir log(n). J'ai donc commencé à faire des expériences avec des petitsn .

Première remarque: un tableau de bits rassemble les mêmes informations que le tableau de bits complémentaire. Par conséquent, tous les tableaux de bits de ma solution ont au plus n/2un bit actif.

n = 3:

Comme nous ne pouvons utiliser un ensemble de bits qu'avec 1 bit actif, la solution optimale dépend de deux appels. Par exemple P([1, 0, 0])et P([0, 1, 0]). Les résultats nous indiquent le premier et le deuxième nombre de la permutation, indirectement nous obtenons le troisième.

n = 4:

Ici, ça devient un peu intéressant. Nous pouvons maintenant utiliser deux types de tableaux de bits. Ceux avec 1 bit actif et ceux avec 2 bits actifs. Si nous utilisons un ensemble de bits avec un bit actif, nous ne recueillons que des informations sur un numéro de la permutation, et retombons n = 3, ce qui entraîne des 1 + 2 = 3appels de P. La partie intéressante est que nous pouvons faire la même chose avec seulement 2 appels, si nous utilisons des tableaux de bits avec 2 bits actifs. Par exemple P([1, 1, 0, 0])etP([1, 0, 1, 0]) .

Disons que nous obtenons les sorties [1, 0, 0, 1]et [0, 0, 1, 1]. Nous voyons que le bit numéro 4 est actif dans les deux tableaux de sortie. Le seul bit qui était actif dans les deux tableaux d'entrée était le bit numéro 1, donc clairement la permutation commence par 4. Il est maintenant facile de voir que le bit 2 a été déplacé vers le bit 1 (première sortie) et le bit 3 a été déplacé vers le bit 3 (deuxième sortie). Par conséquent, la permutation doit être [4, 1, 3, 2].

n = 7:

Maintenant quelque chose de plus grand. Je vais montrer les appels de Pimmédiatement. Ce sont les fois que j'ai inventés après un peu de réflexion et d'expérimentation. (Notez que ce ne sont pas ceux que j'utilise dans mon code.)

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

Si dans les deux premiers tableaux de sortie (et non dans le troisième) le bit 2 est actif, nous savons que la permutation déplace le bit 1 vers le bit 2, car le bit un est le seul bit actif dans les deux premiers tableaux d'entrée.

L'important est que (interprétant les entrées comme une matrice) chacune des colonnes soit unique. Cela m'a rappelé les codes de Hamming , où la même chose est accomplie. Ils prennent simplement les nombres 1 à 7 et utilisent leur représentation binaire comme colonnes. Je vais utiliser les chiffres de 0 à 6. Maintenant, la bonne partie, nous pouvons interpréter les sorties (encore une fois les colonnes) comme des nombres. Ceux-ci nous indiquent le résultat de la permutation appliquée [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]

Nous n'avons donc qu'à retracer les chiffres. Le bit 0 a fini dans le bit 5, le bit 1 a fini dans le bit 0, le bit 2 a fini dans le bit 6, ... Donc la permutation était [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

Et le code de la fonction de permutation:

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
Jakube
la source
1
Si vous remplacez *sltQ]0par m0sltQ, vous pourriez avoir 6 cartes imbriquées de la même longueur.
isaacg
Vous devez, conformément au défi, affecter votre code qui résout le défi à une fonction idéalement nommée fbien que d'autres noms soient autorisés. L'affectation compte pour votre score.
FUZxxl
@FUZxxl a mis à jour mon code. gDéfinit maintenant une fonction au lieu de lire à partir de STDIN.
Jakube
2

Mathematica, 63 + 100 = 163

J'utilise des permutations à base unique, car c'est ainsi que l'indexation fonctionne dans Mathematica.

Tout d'abord, le harnais de test. Voici la fonction de requête p(les noms définis par l'utilisateur ne doivent pas être en majuscules dans Mathematica):

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

Et la préparation des entrées avec la boucle de 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]

Et enfin, ma soumission actuelle qui utilise l'algorithme naïf pour l'instant:

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

Ou avec indentation:

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