introduction
Supposons que l'on vous remette une permutation aléatoire d' n
objets. 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 à n
des objets distincts, vous pourriez immédiatement en déduire son identité. Cependant, vous n'êtes autorisé à appliquer la permutation qu'aux n
vecteurs binaires de longueur , ce qui signifie que vous devrez l'appliquer plusieurs fois pour la reconnaître. Clairement, l'appliquer aux n
vecteurs avec un seul 1
fait 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 n
et une permutation p
des premiers n
entiers, en utilisant une indexation basée sur 0 ou 1. Sa sortie est la permutation p
. Cependant, vous n'êtes pas autorisé à accéder p
directement à la permutation . La seule chose que vous pouvez en faire est de l'appliquer à n'importe quel vecteur de n
bits. À cet effet, vous devez utiliser une fonction auxiliaire P
qui prend une permutation p
et 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 3
et -4
, ou 'a'
et 'b'
, et elles n'ont pas besoin d'être fixées, vous pouvez donc appeler P
avec les deux [-4,3,3,-4]
et [2,2,2,1]
dans le même appel à f
. La définition de P
n'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.txt
qui 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' Integer
objets à P
et comparer leurs identités), et la fonction P
renvoie toujours un nouveau vecteur au lieu de réorganiser son entrée. Vous pouvez modifier librement les noms de f
et 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 P
qui 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 P
d'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.
abaaabababaa
et-4 3 3 3 -4 3
serait un vecteur de bits.Réponses:
J,
44,069322,0693 =3715 + 7,06931Si nous ne pouvons pas appeler
P
suri. n
, nous pouvons au moins appelP
sur chaque biti. n
séparément. Le nombre d' appels deP
est>. 2 ^. n
(⌈log 2 n ⌉). Je pense que c'est optimal.Voici une implémentation de la fonction
P
qui utilise le vecteur de permutationp
et enregistre le nombre d'appels dansPinv
.Voici un harnais de test qui reçoit une permutation et renvoie le nombre d'appels de
p
:Et voici comment vous pouvez l'utiliser sur le fichier
permutations.txt
:Explication
La brève explication est déjà fournie ci-dessus, mais en voici une plus détaillée. Tout d'abord,
f
avec des espaces insérés et comme fonction explicite:Lis:
où 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
P
suri. n
(c. -à0 1 2 ... (n - 1)
), nous invoquonsP
sur chaque position binaire des nombres dansi. 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 de0
ày - 1
.#: y
-y
représenté en base 2. Il est étendu aux vecteurs de nombres de manière naturelle. Par exemple,#: i. 16
donne:#. y
-y
interprété comme un nombre de base 2. C'est notamment l'inverse de#:
;y ~: #. #:
tient toujours.|: y
-y
transposé.u&.v y
-u
sousv
, c'est làvinv u v y
oùvinv
est l'inverse dev
. Notez que|:
c'est son propre inverse.P y
- la fonctionP
appliquée à chaque vecteury
par définition.la source
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.Et puis le code, qui détermine la permutation.
Ceci définit une fonction
g
, qui prend deux arguments. Vous pouvez l'appeler parg5[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
P
compte 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 desint(log2(n-1)) + 1
appels (= ceil(log2(n))
). Etsum(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/2
un 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])
etP([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 des1 + 2 = 3
appels deP
. 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 exempleP([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 par4
. 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
P
immé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.)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]
.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]
.Et le code de la fonction de permutation:
la source
*sltQ]0
parm0sltQ
, vous pourriez avoir 6 cartes imbriquées de la même longueur.f
bien que d'autres noms soient autorisés. L'affectation compte pour votre score.g
Définit maintenant une fonction au lieu de lire à partir de STDIN.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):Et la préparation des entrées avec la boucle de test:
Et enfin, ma soumission actuelle qui utilise l'algorithme naïf pour l'instant:
Ou avec indentation:
la source