Bijektive Funktion ℤ → ℤⁿ


23

Es ist trivial möglich, eine bijektive Funktion von Z (der Menge aller ganzen Zahlen) bis (z. B. die Identitätsfunktion) zu erstellen .Z

Es ist auch möglich, eine bijektive Funktion von bis zu erstellen (die Menge aller Paare von 2 ganzen Zahlen; das kartesische Produkt von undZZ2ZZ ) . Zum Beispiel könnten wir das Gitter nehmen, das ganzzahlige Punkte in einer 2D-Ebene darstellt, eine Spirale von 0 nach außen zeichnen und dann Paare von ganzen Zahlen als Abstand entlang der Spirale codieren, wenn es diesen Punkt schneidet.

Spiral

(Eine Funktion, die dies mit natürlichen Zahlen ausführt, wird als Pairing-Funktion bezeichnet .)

Tatsächlich gibt es eine Familie dieser bijektiven Funktionen:

fk(x):ZZk

Die Herausforderung

Definieren Sie eine Familie von Funktionen (wobei eine positive ganze Zahl ist) mit der Eigenschaft, dass ganze Zahlen bijektiv auf abbildetfk(x)kfk(x)k Tupeln von Ganzzahlen zuordnet.

Ihre Vorlage sollte, da Eingaben und , Rückkehrkxfk(x) .

Das ist , also gewinnt die kürzeste gültige Antwort (gemessen in Bytes).

Spezifikationen

  • Beliebige Familiefk(x) kann verwendet werden, solange sie die obigen Kriterien erfüllt.
  • Es wird empfohlen, eine Beschreibung der Funktionsweise Ihrer Funktionsfamilie sowie ein Snippet hinzuzufügen, um die Inverse der Funktion zu berechnen (dies ist nicht in Ihrer Byteanzahl enthalten).
  • Es ist in Ordnung, wenn die inverse Funktion nicht berechenbar ist, solange Sie nachweisen können, dass die Funktion bijektiv ist.
  • Sie können jede geeignete Darstellung für Ganzzahlen mit Vorzeichen und Listen mit Ganzzahlen mit Vorzeichen für Ihre Sprache verwenden, aber Sie müssen zulassen, dass Eingaben in Ihre Funktion nicht eingeschränkt werden.
  • Sie müssen nur Werte von bis 127 unterstützen.k

Ist es in Ordnung, eine String-Version von kund xanstelle von ganzen Zahlen zu verwenden?
JungHwan Min

@JungHwanMin Zeichenfolgen, die die eingegebenen Zahlen darstellen, sind in Ordnung.
Esolanging Fruit

Antworten:


19

Alice , 14 12 Bytes

/O
\i@/t&Yd&

Probieren Sie es online!

Inverse Funktion (nicht golfen):

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

Probieren Sie es online!

Erläuterung

Alice hat eine eingebaute Bijektion zwischen und 2 , die mit Y(auspacken) und seiner Umkehrung Z (packen) berechnet werden kann . Hier ist ein Auszug aus den Dokumenten, in denen die Bijektion erklärt wird:

Die Details der Bijektion sind für die meisten Anwendungsfälle wahrscheinlich irrelevant. Der wichtigste Punkt ist, dass der Benutzer zwei Ganzzahlen in einer codieren und die beiden Ganzzahlen später erneut extrahieren kann. Durch wiederholtes Anwenden des Befehls pack können ganze Listen oder ganze Zahlenbäume in einer einzigen Zahl gespeichert werden (obwohl dies nicht besonders speichereffizient ist). Die durch die Packoperation berechnete Abbildung ist eine bijektive Funktion 2 → ℤ (dh eine Eins-zu-Eins-Abbildung). Zunächst werden die Ganzzahlen {..., -2, -1, 0, 1, 2, ...} auf die natürlichen Zahlen (einschließlich Null) wie {..., 3, 1, 0, 2, 4 abgebildet , ...} (mit anderen Worten, negative Ganzzahlen werden ungeraden natürlichen und nicht -negative ganze Zahlen sind sogar natürlichen Werten zugeordnet). Die beiden natürlichen Zahlen werden dann über die Taste 1 zugeordnetCantor Pairing-Funktion , bei der die Naturals entlang der Diagonalen des ersten Quadranten des Ganzzahlgitters geschrieben werden. Insbesondere {(0,0), (1,0), (0,1), (2,0), (1,1), (0,2), (3,0), ...} sind zugeordnet auf {0, 1, 2, 3, 4, 5, 6, ...} . Die resultierende natürliche Zahl wird dann unter Verwendung der Umkehrung der früheren Bijektion auf die ganzen Zahlen zurück abgebildet. Der Befehl unpack berechnet genau die Umkehrung dieser Zuordnung.

Wie oben erwähnt, können wir diese auspacken Betrieb verwenden , um Karte z zu z k als auch. Nachdem wir es auf die anfängliche Ganzzahl angewendet haben, können wir die zweite Ganzzahl des Ergebnisses wieder entpacken, wodurch wir eine Liste mit drei Ganzzahlen erhalten. Also geben k-1- Anwendungen von Yuns k ganze Zahlen als Ergebnis.

Wir können die Inverse berechnen, indem wir die Liste Zvon Ende an mit packen .

Das Programm selbst hat also diese Struktur:

/O
\i@/...d&

Dies ist nur eine grundlegende Vorlage für ein Programm, das eine variable Anzahl von Dezimalzahlen als Eingabe liest und als Ergebnis eine variable Anzahl ausgibt. Der eigentliche Code ist also wirklich nur:

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

Eine Sache, die ich ansprechen möchte, ist: "Warum sollte Alice ein eingebautes ℤ → ℤ 2- Bijection haben, ist das nicht ein Gebiet der Golfsprache ?" Wie bei den meisten von Alices weirder Einbauten, ist der Hauptgrund Alices Konstruktionsprinzip , dass jeder Befehl hat zwei Bedeutungen, eine für Cardinal (integer) Modus und einen für Ordinal (string) Modus, und diese beiden Bedeutungen werden sollte irgendwie im Zusammenhang zu geben , Kardinal- und Ordinalmodus das Gefühl, dass sie Spiegeluniversen sind, in denen die Dinge irgendwie gleich, aber auch verschieden sind. Und ziemlich oft hatte ich einen Befehl für einen der beiden Modi, die ich hinzufügen wollte, und musste dann herausfinden, mit welchem ​​anderen Befehl ich ihn koppeln wollte.

Im Fall von Yund war der ZOrdnungsmodus das erste: Ich wollte eine Funktion zum Verschachteln von zwei Zeichenfolgen (zip) und zum erneuten Trennen (unzip). Die Qualität davon, die ich im Kardinalmodus erfassen wollte, bestand darin, eine Ganzzahl aus zwei zu bilden und die beiden Ganzzahlen später wieder extrahieren zu können, was eine solche Bijektion zur natürlichen Wahl macht.

Ich dachte mir auch, dass dies außerhalb des Golfsports sehr nützlich wäre, da Sie damit eine ganze Liste oder sogar einen ganzen Baum von Zahlen in einer einzigen Speichereinheit (Stapelelement, Bandzelle oder Gitterzelle) speichern können.


Tolle Erklärung wie immer
Luis Mendo

Das Finden von Yund Zin den Alice-Dokumenten hat mich tatsächlich dazu veranlasst, diese Herausforderung zu posten (ich hatte eine Weile darüber nachgedacht, aber das erinnerte mich daran).
Esolanging Fruit

11

Python, 96 93 Bytes

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

Dies funktioniert im Prinzip, indem die eingegebene Zahl xin ausgeglichenes ternäres umgewandelt wird und dann die am wenigsten signifikanten Trits (ternären Ziffern) zuerst auf die verschiedenen Koordinaten in einer Round-Robin-Art und Weise verteilt werden. So würde k=2beispielsweise jeder geradzahlig positionierte Trit zur xKoordinate beitragen , und jeder ungeradzahlig positionierte Trit würde zur yKoordinate beitragen . Denn k=3du würdest den ersten, vierten und siebten Trit (etc ...) dazu beitragen x, während der zweite, fünfte und achte dazu beitragen yund der dritte, sechste und neunte dazu beitragen z.

Zum Beispiel mit k=2, schauen wir uns an x=35. Im ausgeglichenen ternären 35ist 110T(unter Verwendung der Wikipedia-Artikel-Notation, wo Teine -1Ziffer darstellt). Das Aufteilen der Trits ergibt 1T(das erste und dritte Trit, von rechts gezählt) für die xKoordinate und 10(das zweite und vierte Trit) für die yKoordinate. Wenn wir jede Koordinate zurück in eine Dezimalzahl konvertieren, erhalten wir 2, 3.

Natürlich konvertiere ich im Golf-Code nicht die ganze Zahl auf einmal in einen ausgeglichenen Ternär. Ich berechne nur einen Trit nach dem anderen (in der vVariablen) und addiere seinen Wert direkt zur entsprechenden Koordinate.

Hier ist eine ungolfed inverse Funktion, die eine Liste von Koordinaten aufnimmt und eine Zahl zurückgibt:

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

Meine fFunktion ist vielleicht bemerkenswert für ihre Leistung. Es verwendet nur O(k)Speicher und benötigt O(k) + O(log(x))Zeit, um die Ergebnisse zu finden, sodass es mit sehr großen Eingabewerten arbeiten kann. Versuchen Sie f(10000, 10**10000)zum Beispiel, und Sie werden eine Antwort ziemlich sofort (Hinzufügen einer zusätzliche Null auf die Exponenten so erhalten xist 10**100000macht es dauert 30 Sekunden oder so auf meinem alten PC). Die Umkehrfunktion ist nicht so schnell, vor allem, weil es schwierig ist zu erkennen, wann sie abgeschlossen ist (sie scannt alle Koordinaten nach jeder Änderung und benötigt daher etwas O(k*log(x))Zeit). Es könnte wahrscheinlich optimiert werden, um schneller zu sein, aber es ist wahrscheinlich schnell genug für normale Parameter.


Sie können die Leerzeichen (Zeilenumbrüche) in der while-Schleife entfernen
Mr. Xcoder

Danke, ich hatte fälschlicherweise gedacht, dass es eine Art Konflikt zwischen einer Schleife und der Verwendung ;von Kettenanweisungen in einer einzelnen Zeile gibt.
Blckknght

9

Schale , 10 Bytes

§~!oΠR€Θݱ

Probieren Sie es online!

Die Umkehrfunktion beträgt ebenfalls 10 Bytes.

§o!ȯ€ΠRΘݱ

Probieren Sie es online!

Erläuterung

Vorwärtsrichtung:

§~!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]

Umgekehrte Richtung:

§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

Das eingebaute kartesische Produkt Πverhält sich gut für unendliche Listen und zählt jedes k- Tupel genau einmal auf.


[[0,1,-1,..],[[0,1,-1,..],[[0,1,-1,..]]soll das Teil sein [[0,1,-1,..],[0,1,-1,..],[0,1,-1,..]]?
Erik der Outgolfer

@EriktheOutgolfer Umm ja, jetzt behoben.
Zgarb

Das ist schön. Wissen Sie als J-Programmierer, ob es eine gute Möglichkeit gibt, eine Lazy-List-Lösung wie diese in J zu konvertieren, die sie nicht unterstützt? ^:^:_Typ-Lösungen enden in der Regel viel umständlicher ...
Jonah

@Jonah Ich bin nicht sicher. Sie könnten versuchen, das Array aller k- Tupel mit Einträgen aus zu berechnen i: xund es nach der Summe der absoluten Werte zu sortieren, und dann in diese indizieren. Die Idee ist, dass diese Arrays Präfixe eines "unendlichen Arrays" sind, das alle k- Tupel enthält .
Zgarb

7

Wolfram Language (Mathematica) , 61 Bytes

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

Probieren Sie es online!

(Nimmt die ganze Zahl und dann die Länge des Tupels als Eingabe.)

Inverse:

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

Probieren Sie es online!

Wie es funktioniert

Die Idee ist einfach: Wir wandeln die Ganzzahleingabe in eine positive Ganzzahl um (indem wir 0,1,2,3, ... auf 1,3,5,7, ... und -1, -2, -3, ... bis 2,4,6, ...) und dann in alle k- Tupel indexieren, sortiert nach Abstand vom Ursprung und dann nach Mathematicas Standardbindungsbruch.

Aber wir können nicht eine unendliche Liste verwenden, so dass , wenn wir für die gesuchte n - te k - tupel, wir nur erzeugen k -Tupeln von ganzen Zahlen im Bereich {- n , ..., n }. Dies ist garantiert ausreichend, da das n - kleinste k- Tupel nach Norm eine Norm kleiner als n hat und alle Tupel der Norm n oder kleiner in dieser Liste enthalten sind.

Für die Umkehrung erzeugen wir einfach eine ausreichend lange Liste von k -Tupeln, finden die Position des gegebenen k -Tupels in dieser Liste und invertieren dann die "Faltung in eine positive Ganzzahl" -Operation.


2
Laufen mit Eingängen [15, 5]stürzte mein PC ...
JungHwan Min

2
Das wird passieren Im Prinzip funktioniert der Algorithmus für alles, aber in Ihrem Fall funktioniert er, indem alle 5-Tupel aus dem Bereich {-31, .., 31} generiert und dann das 31. genommen werden, was ziemlich speicherintensiv ist.
Mischa Lawrow

3

J, 7 Bytes

#.,|:#:

Der J-Code dafür ist peinlich einfach

Eine sehr einfache Paarungsfunktion (oder Tupelungsfunktion) besteht darin, die Ziffern der binären Erweiterung jeder der Zahlen einfach zu verschachteln. So (47, 79)würde zum Beispiel als solche gepaart werden:

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

oder 6399. Offensichtlich können wir trivial auf jedes n-Tupel verallgemeinern.

Lassen Sie uns untersuchen, wie dies Verb für Verb funktioniert.

#:ist anti-base two, bei monadischer Verwendung wird die binäre Erweiterung einer Zahl zurückgegeben. #: 47 79gibt das Ergebnis:

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

|:ist der Transponierungsoperator, der einfach ein Array dreht. Drehen des Ergebnisses von #: 47 79gibt:

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

Wenn ,der Ravel-Operator monadisch verwendet wird, erstellt er eine eindimensionale Liste aus einer Tabelle:

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

Schließlich #.wandelt die Binärentwicklung zurück, uns das Ergebnis geben 6339.

Diese Lösung funktioniert für eine beliebige Folge von ganzen Zahlen.


7
Wie funktioniert das bei negativen Zahlen?
Neil

2

Perl 6 , 148 Bytes

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]}}}

Probieren Sie es online!

Ungolfed:

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*$_] }
    }
}

Erläuterung:

  • rect($n)ist eine Hilfsfunktion, die die Koordinaten der Integralpunkte am Rand eines Rechtecks ​​von Koordinaten (-$n,$n)bis erzeugt ($n, $n).

  • @spiral ist eine langsame, unendliche Liste der Integralpunkte an den Kanten von Rechtecken zunehmender Größe, beginnend mit 0.

  • f($k)$kGibt eine Funktion zurück, bei der es sich um eine Bijektion von Ganzzahlen zu -tupeln von Ganzzahlen handelt.

Wenn $kist 1, wird fdie Identitätszuordnung zurückgegeben -> $_ { $_ }.

Andernfalls &gerfolgt die rekursiv erhaltene Zuordnung von den Ganzzahlen zu $k-1-tupeln von Ganzzahlen.

Dann verlassen wir @spiralden Ursprung und bilden an jedem Punkt ein $kTupel, indem wir die X-Koordinate und das abgeflachte Ergebnis des Aufrufs gmit der Y-Koordinate nehmen. Diese träge erzeugte Zuordnung wird im Array gespeichert @v.

@venthält alle $k-Tupel, die mit dem Index 0 beginnen. Um die Indizierung auf die negativen Ganzzahlen zu erweitern, ordnen wir den geraden Zahlen nur positive und den ungeraden Zahlen negative Eingaben zu. Es wird eine Funktion (Closure) zurückgegeben, die Elemente @vauf diese Weise nachschlägt.


2

JavaScript, 155 Byte

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 verschönern:

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;
  });
}
  • Zunächst ordnen wir alle Ganzzahlen nacheinander allen nicht negativen Ganzzahlen zu:
    • wenn n> 0 ist, ist das Ergebnis n * 2
    • sonst Ergebnis = -n * 2 - 1
  • Zweitens geben wir allen Tupeln mit nicht negativen ganzen Zahlen der Länge k eine Reihenfolge:
    • Summe aller Elemente berechnen, kleineres steht an erster Stelle
    • Wenn die Summe gleich ist, vergleichen Sie von links nach rechts, der kleinere Wert steht an erster Stelle
    • Als Ergebnis haben wir die Zuordnung für alle nicht negativen Ganzzahlen zu Tupeln mit k nicht negativen Ganzzahlen erhalten
  • Ordnen Sie schließlich nicht negative Ganzzahlen im Tupel, die im zweiten Schritt angegeben wurden, allen Ganzzahlen mit ähnlicher Formel im ersten Schritt zu

Ich denke, x<0?~x-x:x+xspart 2 Bytes.
Neil

2

Wolfram Language (Mathematica) , 107 Bytes

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

Probieren Sie es online!

Inverse 60 Bytes

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

Probieren Sie es online!

Erläuterung:

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

N0 -> N0 ^ 2 über Inverse der Pairing-Funktion

N0 -> N0 ^ k Wende das Obige wiederholt auf die Zahl ganz links an, bis wir Länge bekommen k

N0 ^ k -> Z ^ k über f(n) = (-1)^n * ceil(n/2)elementweise


Mathematica, 101 Bytes

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

Ähnlich wie oben (verwendet N anstelle von N0), verwendet jedoch die Umkehrung der Bijektion f: N ^ 2 -> N via f(a, b) = 2^(a - 1)(2b - 1)


Du meinst ... gibt es dafür kein Mathematica eingebaut (wenn Alice eins hat)? Ich bin sprachlos.
JayCe

1

JavaScript, 112 Bytes

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. in nicht-negativ konvertieren
  2. (n * k + i) Ziffer bis i-te Zahl
  3. konvertieren zurück

@HermanLauenstein muss nicht zurückdrehen?
Dienstag,

Ich denke, x<0?~x-x:x+xspart 2 Bytes.
Neil

-5 Bytes mit [...BT${x<0?~x-x:x+x}BT].reverse().map((c,i)=>r[i%k]+=c),(Gutschrift auf @Neil für x<0?~x-x:x+x). .reverse()wird anstelle von verwendet, (s.length-i)da der zusätzliche Parameter sfür den ersten Parameter nicht erforderlich ist .map. Sie müssen nicht zurückkehren, da das temporäre Array nicht erneut verwendet wird. (Ich habe es nicht getestet, aber es sollte wahrscheinlich funktionieren)
Herman L

Ein weiteres Byte kann durch das Ersetzen gespeichert werden .fill('')mit .fill(0), da eine führende Null keinen Unterschied macht (zumindest nicht , wenn sie in Safari getestet)
Herman L

@HermanLauenstein Hast du es versucht .fill`` ? Es könnten noch ein paar Bytes eingespart werden.
Neil


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.