Fonction bijective ℤ → ℤⁿ


23

Il est trivialement possible de créer une fonction bijective de (l'ensemble de tous les entiers) à (par exemple la fonction d'identité).ZZZ

Il est également possible de créer une fonction bijective de à (l'ensemble de toutes les paires de 2 entiers; le produit cartésien de et ). Par exemple, nous pourrions prendre le réseau représentant des points entiers sur un plan 2D, dessiner une spirale de 0 vers l'extérieur, puis encoder des paires d'entiers comme distance le long de la spirale lorsqu'elle coupe ce point.Z 2 Z ZZZ2ZZ

Spirale

(Une fonction qui fait cela avec des nombres naturels est appelée fonction d'appariement .)

En fait, il existe une famille de ces fonctions bijectives:

fk(x):ZZk

Le défi

Définissez une famille de fonctions (où est un entier positif) avec la propriété que mappe bijectivement des entiers à multiplets d'entiers.k f k ( x ) kfk(x)kfk(x)k

Votre soumission doit, compte tenu des entrées et , renvoyer .x f k ( x )kxfk(x)

C'est le , donc la réponse valide la plus courte (mesurée en octets) l'emporte.

Caractéristiques

  • Toute famille peut être utilisée tant qu'elle remplit les critères ci-dessus.fk(x)
  • Nous vous encourageons à ajouter une description du fonctionnement de votre famille de fonctions, ainsi qu'un extrait de code pour calculer l'inverse de la fonction (cela n'est pas inclus dans votre nombre d'octets).
  • C'est bien si la fonction inverse n'est pas calculable, tant que vous pouvez le prouver, la fonction est bijective.
  • Vous pouvez utiliser n'importe quelle représentation appropriée pour les entiers signés et les listes d'entiers signés pour votre langue, mais vous devez autoriser les entrées de votre fonction à être illimitées.
  • Il vous suffit de prendre en charge des valeurs de jusqu'à 127.k

Est-il correct de prendre une version chaîne de ket xau lieu d'entiers?
JungHwan Min

@JungHwanMin Les chaînes représentant les nombres saisis sont correctes.
Esolanging Fruit

Réponses:


19

Alice , 14 12 octets

/O
\i@/t&Yd&

Essayez-le en ligne!

Fonction inverse (non golfée):

/o     Q
\i@/~~\ /dt&Z

Essayez-le en ligne!

Explication

Alice a une bijection intégrée entre et 2 , qui peut être calculée avec Y(décompresser) et son inverse Z (pack). Voici un extrait des documents expliquant la bijection:

Les détails de la bijection ne sont probablement pas pertinents pour la plupart des cas d'utilisation. Le point principal est qu'il permet à l'utilisateur de coder deux entiers en un et d'extraire à nouveau les deux entiers plus tard. En appliquant la commande pack à plusieurs reprises, des listes entières ou des arbres d'entiers peuvent être stockés dans un seul numéro (mais pas d'une manière particulièrement efficace en mémoire). Le mappage calculé par l'opération pack est une fonction bijective 2 → ℤ (c'est-à-dire un mappage un à un). Tout d'abord, les entiers {..., -2, -1, 0, 1, 2, ...} sont mappés aux nombres naturels (y compris zéro) comme {..., 3, 1, 0, 2, 4 , ...}(en d'autres termes, les entiers négatifs sont mappés sur des naturels impairs et les entiers non négatifs sont mappés sur des naturels pairs). Les deux nombres naturels sont ensuite mappés en un via la fonction d'appariement de Cantor , qui écrit les valeurs naturelles le long des diagonales du premier quadrant de la grille entière. Plus précisément, {(0,0), (1,0), (0,1), (2,0), (1,1), (0,2), (3,0), ...} sont mappé à {0, 1, 2, 3, 4, 5, 6, ...} . Le nombre naturel résultant est ensuite mappé vers les entiers en utilisant l'inverse de la bijection précédente. La commande unpack calcule exactement l'inverse de ce mappage.

Comme mentionné ci-dessus, nous pouvons également utiliser cette opération de décompression pour mapper à k . Après l'avoir appliqué à l'entier initial, nous pouvons décompresser à nouveau le deuxième entier du résultat, ce qui nous donne une liste de trois entiers. Ainsi, k-1 applications de Ynous donnent k entiers comme résultat.

Nous pouvons calculer l'inverse en emballant la liste à Zpartir de la fin.

Le programme lui-même a donc cette structure:

/O
\i@/...d&

Ceci est juste un modèle de base pour un programme qui lit un nombre variable d'entiers décimaux en entrée et imprime un nombre variable comme résultat. Donc, le code réel est vraiment juste:

t   Decrement k.
&   Repeat the next command k-1 times.
Y   Unpack.

Une chose que je voudrais aborder est "pourquoi Alice aurait-elle une fonction intégrée pour une bijection ℤ → ℤ 2 , n'est-ce pas le territoire de la langue du golf"? Comme avec la plupart des éléments intégrés plus étranges d'Alice, la raison principale est le principe de conception d'Alice selon lequel chaque commande a deux significations, une pour le mode Cardinal (entier) et une pour le mode Ordinal (chaîne), et ces deux significations devraient être en quelque sorte liées pour donner Le mode Cardinal et Ordinal donne l'impression d'être des univers miroirs où les choses sont en quelque sorte les mêmes mais aussi différentes. Et bien souvent, j'avais une commande pour l'un des deux modes que je voulais ajouter, puis je devais trouver quelle autre commande associer.

Dans le cas de Yet le Zmode Ordinal est venu en premier: je voulais avoir une fonction pour entrelacer deux chaînes (zip) et les séparer à nouveau (décompresser). La qualité de ce que je voulais capturer en mode Cardinal était de former un entier sur deux et de pouvoir extraire les deux entiers plus tard, ce qui fait de cette bijection le choix naturel.

J'ai également pensé que cela serait en fait très utile en dehors du golf, car cela vous permet de stocker une liste entière ou même un arbre d'entiers dans une seule unité de mémoire (élément de pile, cellule de bande ou cellule de grille).


Grande explication comme toujours
Luis Mendo

Trouver Yet Zdans les documents d'Alice est en fait ce qui m'a poussé à publier ce défi (j'y pensais depuis un moment, mais cela m'a rappelé).
Esolanging Fruit

11

Python, 96 93 octets

def f(k,x):
 c=[0]*k;i=0
 while x:v=(x+1)%3-1;x=x//3+(v<0);c[i%k]+=v*3**(i//k);i+=1
 return c

Cela fonctionne en principe en convertissant le nombre d'entrée xen ternaire équilibré , puis en répartissant les trits (chiffres ternaires) les moins significatifs en premier entre les différentes coordonnées de façon circulaire. Ainsi, par k=2exemple, chaque trit positionné pair contribuerait à la xcoordonnée, et chaque trit positionné impair contribuerait à la ycoordonnée. Car k=3vous auriez les premier, quatrième et septième trits (etc ...) contribuant à x, tandis que les deuxième, cinquième et huitième contribueraient y, et les troisième, sixième et neuvième contribueraient z.

Par exemple, avec k=2, regardons x=35. En ternaire équilibré, 35est 110T(en utilisant la notation de l'article de Wikipedia où Treprésente un -1chiffre). La division des trits donne 1T(les premier et troisième trits, en partant de la droite) pour les xcoordonnées et 10(deuxième et quatrième trits) pour les ycoordonnées. La conversion de chaque coordonnée en décimale, nous obtenons 2, 3.

Bien sûr, je ne convertis pas le nombre entier en ternaire équilibré à la fois dans le code golfé. Je calcule simplement un trit à la fois (dans la vvariable) et j'ajoute sa valeur directement à la coordonnée appropriée.

Voici une fonction inverse non golfée qui prend une liste de coordonnées et renvoie un nombre:

def inverse_f(coords):
    x = 0
    i = 0
    while any(coords):
        v = (coords[i%3]+1) % 3 - 1
        coords[i%3] = coords[i%3] // 3 + (v==-1)
        x += v * 3**i
        i += 1
    return x

Ma ffonction est peut-être remarquable pour ses performances. Il utilise uniquement de la O(k)mémoire et prend du O(k) + O(log(x))temps pour trouver les résultats, il peut donc fonctionner avec de très grandes valeurs d'entrée. Essayez f(10000, 10**10000)par exemple, et vous obtiendrez une réponse à peu près instantanément ( en ajoutant un zéro supplémentaire à l'exposant donc xest le 10**100000rend prendre 30 secondes environ sur mon ancien PC). La fonction inverse n'est pas aussi rapide, principalement parce qu'il lui est difficile de dire quand c'est fait (elle scanne toutes les coordonnées après chaque changement, donc cela prend quelque chose comme du O(k*log(x))temps). Il pourrait probablement être optimisé pour être plus rapide, mais il est probablement déjà assez rapide pour les paramètres normaux.


Vous pouvez supprimer les espaces (sauts de ligne) à l' intérieur de la boucle while
M. Xcoder

Merci, j'avais pensé à tort qu'il y avait une sorte de conflit entre une boucle et l'utilisation ;pour enchaîner des instructions sur une seule ligne.
Blckknght

9

Husk , 10 octets

§~!oΠR€Θݱ

Essayez-le en ligne!

La fonction inverse est également de 10 octets.

§o!ȯ€ΠRΘݱ

Essayez-le en ligne!

Explication

Direction avant:

§~!oΠR€Θݱ  Implicit inputs, say k=3 and x=-48
        ݱ  The infinite list [1,-1,2,-2,3,-3,4,-4,..
       Θ    Prepend 0: [0,1,-1,2,-2,3,-3,4,-4,..
 ~    €     Index of x in this sequence: 97
§    R      Repeat the sequence k times: [[0,1,-1,..],[0,1,-1,..],[0,1,-1,..]]
   oΠ       Cartesian product: [[0,0,0],[1,0,0],[0,1,0],[1,1,0],[-1,0,0],[0,0,1],..
  !         Index into this list using the index computed from x: [-6,1,0]

Direction inverse:

§o!ȯ€ΠRΘݱ  Implicit inputs, say k=3 and y=[-6,1,0]
     ΠRΘݱ  As above, k-wise Cartesian product of [0,1,-1,2,-2,..
   ȯ€       Index of y in this sequence: 97
§o!         Index into the sequence [0,1,-1,2,-2,.. : -48

Le produit cartésien intégré Πse comporte bien pour les listes infinies, énumérant chaque k- tuple exactement une fois.


[[0,1,-1,..],[[0,1,-1,..],[[0,1,-1,..]]cette partie est-elle censée être [[0,1,-1,..],[0,1,-1,..],[0,1,-1,..]]?
Erik the Outgolfer

@EriktheOutgolfer Umm ouais, corrigé maintenant.
Zgarb

C'est beau. En tant que programmeur J, savez-vous s'il existe un bon moyen de convertir une solution de liste paresseuse comme celle-ci en J, qui ne les prend pas en charge? ^:^:_les solutions de type finissent généralement par être beaucoup plus lourdes ...
Jonah

@Jonah, je ne suis pas sûr. Vous pouvez essayer de calculer le tableau de tous les k -tuples avec des entrées à partir de i: xet le trier par la somme des valeurs absolues, puis l'indexer. L'idée est que ces tableaux sont des préfixes d'un "tableau infini" qui contient tous les k -tuples.
Zgarb

7

Wolfram Language (Mathematica) , 61 octets

SortBy[Range[-(x=2Abs@#+Boole[#>=0]),x]~Tuples~#2,#.#&][[x]]&

Essayez-le en ligne!

(Prend le nombre entier puis la longueur du tuple en entrée.)

Inverse:

If[OddQ[#],#-1,-#]/2&@Tr@Position[SortBy[Range[-(x=Ceiling@Norm@#),x]~Tuples~Length@#,#.#&],#]&

Essayez-le en ligne!

Comment ça marche

L'idée est simple: nous transformons l'entrée entière en un entier positif (en mappant 0,1,2,3, ... à 1,3,5,7, ... et -1, -2, -3, ... à 2,4,6, ...) puis indexez dans tous les k -uplets, triés par distance de l'origine, puis par bris d'égalité par défaut de Mathematica.

Mais nous ne pouvons pas utiliser une liste infinie, donc quand nous sommes à la recherche pour la n ième k uplet, nous générons que k - uplets d'entiers dans la gamme {- n , ..., n }. Ceci est garanti suffisant, car le n ème plus petit k- multiplet par norme a une norme inférieure à n , et tous les tuples de norme n ou moins sont inclus dans cette liste.

Pour l'inverse, nous générons simplement une liste suffisamment longue de k- uplets, trouvons la position du k- tuple donné dans cette liste, puis inversons l'opération "repli en un entier positif".


2
Courir avec des entrées a fait [15, 5]planter mon PC ...
JungHwan Min

2
Ça va arriver. En principe, l'algorithme fonctionne pour n'importe quoi, mais dans votre cas, il fonctionne en générant tous les 5 tuples à partir de la plage {-31, .., 31} puis en prenant le 31, donc c'est assez gourmand en mémoire.
Misha Lavrov

3

J, 7 octets

#.,|:#:

Le code J pour faire cela d'une manière embarrassante simple

Une fonction d'appariement très simple (ou fonction de tuilage) consiste à simplement entrelacer les chiffres de l'expansion binaire de chacun des nombres. Ainsi, par exemple, (47, 79)serait jumelé en tant que tel:

1_0_0_1_1_1_1
 1_0_1_1_1_1
-------------
1100011111111

ou, 6399. Évidemment, nous pouvons généraliser trivialement à n'importe quel n-tuple.

Examinons comment cela fonctionne verbe par verbe.

#:est anti-base deux, lorsqu'il est utilisé de façon monadique, il renvoie l'expansion binaire d'un nombre. #: 47 79donne le résultat:

0 1 0 1 1 1 1
1 0 0 1 1 1 1

|:est l'opérateur de transposition, qui fait simplement pivoter un tableau. La rotation du résultat de #: 47 79donne:

0 1
1 0
0 0
1 1
1 1
1 1
1 1

Lorsqu'il est utilisé de façon monadique, ,est l'opérateur Ravel, il produit une liste à 1 dimension à partir d'une table:

0 1 1 0 0 0 1 1 1 1 1 1 1 1

Enfin, #.reconvertit l'expansion binaire, nous donnant le résultat 6339.

Cette solution fonctionnera pour n'importe quelle chaîne d'entiers.


7
Comment cela fonctionne-t-il pour les nombres négatifs?
Neil

2

Perl 6 , 148 octets

my@s=map ->\n{|grep {n==abs any |$_},(-n..n X -n..n)},^Inf;my&f={$_==1??+*!!do {my&g=f $_-1;my@v=map {.[0],|g .[1]},@s;->\n{@v[n>=0??2*n!!-1-2*n]}}}

Essayez-le en ligne!

Non golfé:

sub rect($n) {
    grep ->[$x,$y] { abs($x|$y) == $n }, (-$n..$n X -$n..$n);
}

my @spiral = map { |rect($_) }, ^Inf;

sub f($k) {
    if ($k == 1) {
        -> $_ { $_ }
    } else {
        my &g = f($k-1);
        my @v = map -> [$x, $y] { $x, |g($y) }, @spiral;
        -> $_ { $_ >= 0 ?? @v[2*$_] !! @v[-1-2*$_] }
    }
}

Explication:

  • rect($n)est une fonction d'aide qui génère les coordonnées des points intégraux sur le bord d'un rectangle de coordonnées (-$n,$n)à ($n, $n).

  • @spiral est une liste paresseuse et infinie des points intégraux sur les bords de rectangles de taille croissante, à partir de 0.

  • f($k)renvoie une fonction qui est une bijection des entiers en $k-tuples d'entiers.

Si $kest 1, frenvoie le mappage d'identité -> $_ { $_ }.

Sinon, &gc'est le mappage obtenu récursivement des entiers aux $k-1-tuples d'entiers.

Ensuite, nous @spiralsortons de l'origine, et à chaque point formons un $k-tuple en prenant la coordonnée X et le résultat aplati de l'appel gavec la coordonnée Y. Ce mappage généré paresseusement est stocké dans le tableau @v.

@vcontient tous les $k-tuples commençant par l'index 0, donc pour étendre l'indexation aux entiers négatifs, nous mappons simplement les entrées positives aux nombres pairs et les entrées négatives aux nombres impairs. Une fonction (fermeture) est retournée qui recherche les éléments de @vcette manière.


2

JavaScript, 155 octets

f=k=>x=>(t=x<0?1+2*~x:2*x,h=y=>(g=(v,p=[])=>1/p[k-1]?v||t--?0:p.map(v=>v&1?~(v/2):v/2):[...Array(1+v)].map((_,i)=>g(v-i,[...p,i])).find(u=>u))(y)||h(y+1))(0)

Version Prettify:

k => x => {
  // Map input to non-negative integer
  if (x > 0) t = 2 * x; else t = 2 * -x - 1;
  // we try to generate all triples with sum of v
  g = (v, p = []) => {
    if (p.length === k) {
      if (v) return null;
      if (t--) return null;
      // if this is the t-th one we generate then we got it
      return p;
    }
    for (var i = 0; i <= v; i++) {
      var r = g(v-i, [...p, i]);
      if (r) return r;
    }
  }
  // try sum from 0 to infinity
  h = x => g(x) || h(x + 1);
  // map tuple of non-negative integers back
  return h(0).map(v => {
    if (v % 2) return -(v + 1) / 2
    else return v / 2;
  });
}
  • Tout d'abord, nous mappons tous les entiers à tous les entiers non négatifs un par un:
    • si n> 0 alors résultat = n * 2
    • sinon résultat = -n * 2 - 1
  • Deuxièmement, nous donnons un ordre à tous les tuples avec des entiers non négatifs de longueur k:
    • calculer la somme de tous les éléments, le plus petit vient en premier
    • si la somme est égale, comparez de gauche à droite, la plus petite vient en premier
    • En conséquence, nous avons obtenu la carte de tous les entiers non négatifs en tuples avec k entiers non négatifs
  • Enfin, mappez des entiers non négatifs en tuple donnés à la deuxième étape à tous les entiers avec une formule similaire à la première étape

Je pense que x<0?~x-x:x+xsauve 2 octets.
Neil

2

Wolfram Language (Mathematica) , 107 octets

(-1)^#⌈#/2⌉&@Nest[{w=⌊(√(8#+1)-1)/2⌋;x=#-w(w+1)/2,w-x}~Join~{##2}&@@#&,{2Abs@#-Boole[#<0]},#2-1]&

Essayez-le en ligne!

Inverse, 60 octets

(-1)^#⌈#/2⌉&@Fold[+##(1+##)/2+#&,2Abs@#-Boole[#<0]&/@#]&

Essayez-le en ligne!

Explication:

Z -> N0 via f(n) = 2n if n>=0 and -2n-1 if n<0

N0 -> N0 ^ 2 via l' inverse de la fonction d'appariement

N0 -> N0 ^ k Appliquer à plusieurs reprises ce qui précède au nombre le plus à gauche jusqu'à obtenir la longueur k

N0 ^ k -> Z ^ k via f(n) = (-1)^n * ceil(n/2), élément par élément


Mathematica, 101 octets

(-1)^#⌈#/2⌉&@Nest[{a=#~IntegerExponent~2+1,#/2^a+1/2}~Join~{##2}&@@#&,{2Abs@#+Boole[#<=0]},#2-1]&

Similaire à ci-dessus (utilise N au lieu de N0), mais utilise l'inverse de la bijection f: N ^ 2 -> N via f(a, b) = 2^(a - 1)(2b - 1)


Vous voulez dire ... il n'y a pas de Mathematica intégré pour ça (quand Alice en a un)? Je suis sans voix.
JayCe

1

JavaScript, 112 octets

k=>x=>(r=Array(k).fill(''),[...`${x<0?2*~x+1:2*x}`].map((c,i,s)=>r[(s.length-i)%k]+=c),r.map(v=>v&1?~(v/2):v/2))
  1. convertir en non négatif
  2. (n * k + i) ème chiffre au i-ème numéro
  3. reconvertir

@HermanLauenstein n'a pas besoin de revenir en arrière?
tsh

Je pense que x<0?~x-x:x+xsauve 2 octets.
Neil

-5 octets en utilisant [...BT${x<0?~x-x:x+x}BT].reverse().map((c,i)=>r[i%k]+=c),(crédit à @Neil pour x<0?~x-x:x+x). .reverse()est utilisé à la place de (s.length-i)car il évite le besoin du paramètre supplémentaire sau premier .map. Il n'est pas nécessaire de revenir en arrière car le tableau temporaire n'est plus utilisé. (Je ne l'ai pas testé mais cela devrait probablement fonctionner)
Herman L

Un autre octet peut être enregistré en le remplaçant .fill('')par .fill(0), car un zéro non significatif ne fait aucune différence (du moins pas lorsqu'il est testé dans Safari)
Herman L

@HermanLauenstein Avez-vous essayé .fill`` ? Cela pourrait économiser encore quelques octets.
Neil


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.