it-swarm.com.de

Mapping von zwei ganzen Zahlen auf eine eindeutige und deterministische Weise

Stellen Sie sich zwei positive ganze Zahlen A und B vor. Ich möchte diese beiden zu einer einzigen ganzen Zahl C kombinieren. 

Es kann keine anderen Ganzzahlen D und E geben, die sich zu C ..__ kombinieren. Die Kombination mit dem Additionsoperator funktioniert daher nicht. ZB 30 + 10 = 40 = 40 + 0 = 39 + 1. ____________________________________________________________________________________________________________________________ ZB 31 + 2 = 312 = 3 + 12

Diese Kombinationsoperation sollte auch deterministisch sein (immer dasselbe Ergebnis mit den gleichen Eingaben liefern) und sollte immer eine Ganzzahl auf der positiven oder negativen Seite von Ganzzahlen ergeben.

207
harm

Sie suchen nach einer bijektiven NxN -> N-Zuordnung. Diese werden für z. Verzahnung . In this PDF finden Sie eine Einführung in die sogenannten pairing-Funktionen. Wikipedia führt eine spezifische Paarungsfunktion ein, nämlich die Cantor-Paarungsfunktion :

pi(k1, k2) = 1/2(k1 + k2)(k1 + k2 + 1) + k2

Drei Anmerkungen:

  • Wie bereits andere deutlich gemacht haben: Wenn Sie planen, eine Pairing-Funktion zu implementieren, werden Sie bald feststellen, dass Sie beliebig große Ganzzahlen (Bignums) benötigen.
  • Wenn Sie nicht zwischen den Paaren (a, b) und (b, a) unterscheiden möchten, sortieren Sie a und b, bevor Sie die Pairing-Funktion anwenden.
  • Eigentlich habe ich gelogen. Sie suchen nach einer bijektiven ZxZ -> N-Zuordnung. Cantors Funktion funktioniert nur bei nicht negativen Zahlen. Dies ist jedoch kein Problem, da es einfach ist, eine Verknüpfung f : Z -> N zu definieren, wie z. B.:
    • f(n) = n * 2 wenn n> = 0
    • f(n) = -n * 2 - 1 wenn n <0
206
Stephan202

Cantor-Pairing-Funktion ist wirklich eine der besseren, wenn man einfach, schnell und platzsparend ist, aber bei Wolfram von Matthew Szudzik, hier gibt es etwas besseres. Die Einschränkung der Cantor-Paarungsfunktion (relativ) besteht darin, dass der Bereich der codierten Ergebnisse nicht immer innerhalb der Grenzen einer 2N-Bit-Ganzzahl bleibt, wenn die Eingänge zwei N-Bit-Ganzzahlen sind. Das heißt, wenn meine Eingaben zwei 16-Bit-Ganzzahlen sind, die von 0 to 2^16 -1 reichen, sind 2^16 * (2^16 -1) Kombinationen von Eingaben möglich. Daher benötigen wir nach dem offensichtlichen Pigeonhole-Prinzip eine Ausgabe von mindestens 2^16 * (2^16 -1), die gleich 2^32 - 2^16 ist. oder, anders ausgedrückt, eine Abbildung von 32-Bitnummern sollte idealerweise realisierbar sein. Dies ist in der Programmierwelt nicht von geringer praktischer Bedeutung. 

Cantor-Pairing-Funktion:

(a + b) * (a + b + 1) / 2 + a; where a, b >= 0

Das Mapping für zwei maximal 16-Bit-Ganzzahlen (65535, 65535) wird 8589803520 sein, die, wie Sie sehen, nicht in 32 Bit passt.

Geben Sie Szudziks Funktion ein: 

a >= b ? a * a + a + b : a + b * b;  where a, b >= 0

Das Mapping für (65535, 65535) lautet jetzt 4294967295, wobei es sich bei dem Ergebnis um eine 32-Bit-Ganzzahl (0 bis 2 ^ 32 -1) handelt. Hier ist diese Lösung ideal, sie nutzt einfach jeden einzelnen Punkt in diesem Raum aus, sodass nichts effizienter wird. 


In Anbetracht der Tatsache, dass wir uns in der Regel mit signierten Implementierungen von Zahlen verschiedener Größen in Sprachen/Frameworks befassen, betrachten wir signed 16-Bit-Ganzzahlen, die von -(2^15) to 2^15 -1 reichen (später werden wir sehen, wie auch die Ausgabe über den signierten Bereich erweitert werden kann). Da a und b positiv sein müssen, liegen sie zwischen 0 to 2^15 - 1.

Cantor-Pairing-Funktion

Die Abbildung für zwei maximal 16-Bit-Ganzzahlen mit Vorzeichen (32767, 32767) beträgt 2147418112, was knapp unter dem Maximalwert für 32-Bit-Ganzzahlen mit Vorzeichen liegt.

Nun Szudziks Funktion

(32767, 32767) => 1073741823, viel kleiner ..

Lassen Sie uns negative ganze Zahlen berücksichtigen. Das ist jenseits der ursprünglichen Frage, die ich kenne, aber ich arbeite nur daran, zukünftigen Besuchern zu helfen.

Cantor-Pairing-Funktion:

A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
(A + B) * (A + B + 1) / 2 + A;

(-32768, -32768) => 8589803520, was Int64 ist. 64-Bit-Ausgabe für 16-Bit-Eingänge kann so unverzeihlich sein !!

Szudziks Funktion

A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
A >= B ? A * A + A + B : A + B * B;

(-32768, -32768) => 4294967295. Dies ist 32 Bit für den vorzeichenlosen Bereich oder 64 Bit für den vorzeichenbehafteten Bereich, aber immer noch besser.

Jetzt war die Ausgabe immer positiv. In der signierten Welt ist es wäre noch platzsparender, wenn wir die Hälfte der Ausgabe auf die negative Achse übertragen könnten. Für Szudzik könnte man das so machen:

A = a >= 0 ? 2 * a : -2 * a - 1;
B = b >= 0 ? 2 * b : -2 * b - 1;
C = (A >= B ? A * A + A + B : A + B * B) / 2;
a < 0 && b < 0 || a >= 0 && b >= 0 ? C : -C - 1;

(-32768, 32767) => -2147483648

(32767, -32768) => -2147450880

(0, 0) => 0 

(32767, 32767) => 2147418112

(-32768, -32768) => 2147483647

Was ich mache: Nachdem ich ein Gewicht von 2 auf die Eingaben angewendet und die Funktion durchgegangen bin, teile ich den Ausgang durch zwei und nehme einige davon in die negative Achse, indem ich mit -1 multipliziere. 

Siehe die Ergebnisse. Für jede Eingabe im Bereich einer vorzeichenbehafteten 16-Bitnummer liegt die Ausgabe innerhalb der Grenzen einer vorzeichenbehafteten 32-Bit-Ganzzahl, die cool ist. Ich bin mir nicht sicher, wie ich mit der Cantor-Pairing-Funktion auf die gleiche Art und Weise vorgehen soll, habe aber nicht so viel unternommen, wie es nicht so effizient ist. Außerdem bedeuten mehr Berechnungen, die an der Cantor-Pairing-Funktion beteiligt sind, auch die Verlangsamung.

Hier ist eine C # -Implementierung.

public static long PerfectlyHashThem(int a, int b)
{
    var A = (ulong)(a >= 0 ? 2 * (long)a : -2 * (long)a - 1);
    var B = (ulong)(b >= 0 ? 2 * (long)b : -2 * (long)b - 1);
    var C = (long)((A >= B ? A * A + A + B : A + B * B) / 2);
    return a < 0 && b < 0 || a >= 0 && b >= 0 ? C : -C - 1;
}

public static int PerfectlyHashThem(short a, short b)
{
    var A = (uint)(a >= 0 ? 2 * a : -2 * a - 1);
    var B = (uint)(b >= 0 ? 2 * b : -2 * b - 1);
    var C = (int)((A >= B ? A * A + A + B : A + B * B) / 2);
    return a < 0 && b < 0 || a >= 0 && b >= 0 ? C : -C - 1;
}

Da die Zwischenberechnungen die Grenzen der mit 2N vorzeichenbehafteten Ganzzahl überschreiten können, habe ich den 4N-Integertyp verwendet (die letzte Division von 2 bringt das Ergebnis auf 2N zurück).

Der Link, den ich bei einer alternativen Lösung bereitgestellt habe, zeigt einen Graphen der Funktion, bei dem jeder einzelne Punkt im Raum verwendet wird. Es ist erstaunlich zu sehen, dass Sie ein Koordinatenpaar reversibel eindeutig zu einer einzigen Zahl kodieren könnten! Magische Zahlenwelt !!

194
nawfal

Wenn A und B mit 2 Bytes ausgedrückt werden können, können Sie sie zu 4 Bytes zusammenfassen. Setzen Sie A auf die höchstwertige Hälfte und B auf die niedrigste Hälfte.

In der C-Sprache ergibt dies (vorausgesetzt, sizeof (short) = 2 und sizeof (int) = 4):

int combine(short A, short B)
{
    return A<<16 | B;
}

short getA(int C)
{
    return C>>16;
}

short getB(int C)
{
    return C & 0xFFFF;
}
44
mouviciel

Ist das überhaupt möglich?
Sie kombinieren zwei Ganzzahlen. Beide haben den Bereich -2.147.483.648 bis 2.147.483.647, aber Sie werden nur die positiven Werte annehmen .. Das ergibt 2147483647 ^ 2 = 4.61169E + 18 Kombinationen .. Da jede Kombination eindeutig sein muss UND zu einer ganzen Zahl führt, ergeben Sie Ich brauche eine Art magische Ganzzahl, die diese Anzahl von Zahlen enthalten kann. 

Oder ist meine Logik fehlerhaft?

14
Boris Callens

Sei Zahl a die erste, b die zweite. Sei p der a+1-te Primzahl, q der b+1- Primzahl

Das Ergebnis ist dann pq, wenn a<b, oder 2pq wenn a>b. Wenn a=b, so sei p^2.

8
ASk

Die mathematische Standardmethode für positive ganze Zahlen besteht darin, die Einzigartigkeit der Primfaktorzerlegung zu verwenden.

f( x, y ) -> 2^x * 3^y

Der Nachteil ist, dass das Bild dazu neigt, einen großen Bereich von ganzen Zahlen zu umfassen. Wenn Sie also das Mapping in einem Computeralgorithmus ausdrücken, können Sie Probleme mit der Auswahl eines geeigneten Typs für das Ergebnis haben.

Sie können dies ändern, um negative x und y zu behandeln, indem Sie Flags mit Potenzen von 5 und 7 Termen codieren.

z.B.

f( x, y ) -> 2^|x| * 3^|y| * 5^(x<0) * 7^(y<0)
7
CB Bailey

Für positive ganze Zahlen als Argumente und bei denen die Reihenfolge der Argumente keine Rolle spielt:

  1. Hier ist eine ungeordnete Paarungsfunktion :

    <x, y> = x * y + trunc((|x - y| - 1)^2 / 4) = <y, x>
    
  2. Für x ≠ y ist hier eine einzigartige ungeordnete Paarungsfunktion :

    <x, y> = if x < y:
               x * (y - 1) + trunc((y - x - 2)^2 / 4)
             if x > y:
               (x - 1) * y + trunc((x - y - 2)^2 / 4)
           = <y, x>
    
4
ma11hew28

f(a, b) = s(a+b) + a, wobei s(n) = n*(n+1)/2

  • Dies ist eine Funktion - sie ist deterministisch. 
  • Es ist auch injektiv - f bildet verschiedene Werte für verschiedene Paare (a, b) ab. Sie können dies mit der Tatsache beweisen: s(a+b+1)-s(a+b) = a+b+1 < a
  • Es gibt ziemlich kleine Werte zurück - gut, wenn Sie es für die Indexierung von Arrays verwenden möchten, da das Array nicht groß sein muss. 
  • Es ist cachefreundlich - wenn zwei Paare (a, b) nahe beieinander liegen, ordnet f ihnen Zahlen zu, die nahe beieinander liegen (im Vergleich zu anderen Methoden).

Ich habe nicht verstanden, was du meinst mit: 

sollte immer eine ganze Zahl auf .__ ergeben. entweder das Positive oder das Negative Seite von ganzen Zahlen

Wie schreibe ich in dieses Forum (mehr als), (weniger als) Zeichen?

4
libeako

Es ist nicht so schwer, ein Mapping zu erstellen:

 1 2 3 4 5 Verwenden Sie diese Abbildung, wenn (a, b)! = (B, a) 
 1 0 1 3 6 10 
 2 2 4 7 11 16. .4 9 13 18 24 31 5 14 19 25 32 40 

 1 2 3 4 5 verwenden Sie diese Abbildung, wenn (a, b) == (b, a) (Spiegel) .__ 1 1 2 4 6 2 1 3 5 7 10 3 3 5 8 11 14 4 4 8 11 15 19 5 5 10 14 19 24 


 0 1 -1 2 -2 verwenden Sie dies, wenn Sie ein negatives/positives Ergebnis benötigen 
 0 0 1 2 4 6 
 1 1 3 5 7 10 1 2 5 8 11 14 
 2 4 8 11 15 19 
- 2 6 10 14 19 24 

Herauszufinden, wie man den Wert für ein beliebiges a, b erhält, ist etwas schwieriger.

4
Dolphin

Obwohl die Antwort von Stephan202 die einzig wirklich allgemeine Antwort ist, können Sie für Ganzzahlen in einem begrenzten Bereich bessere Ergebnisse erzielen. Wenn Ihr Bereich beispielsweise 0,10.000 beträgt, können Sie Folgendes tun:

#define RANGE_MIN 0
#define RANGE_MAX 10000

unsigned int merge(unsigned int x, unsigned int y)
{
    return (x * (RANGE_MAX - RANGE_MIN + 1)) + y;
}

void split(unsigned int v, unsigned int &x, unsigned int &y)
{
    x = RANGE_MIN + (v / (RANGE_MAX - RANGE_MIN + 1));
    y = RANGE_MIN + (v % (RANGE_MAX - RANGE_MIN + 1));
}

Ergebnisse können in eine einzelne ganze Zahl für einen Bereich bis zur Quadratwurzel der Kardinalität des ganzzahligen Typs passen. Dies ist etwas effizienter als die allgemeinere Methode von Stephan202. Es ist auch wesentlich einfacher zu dekodieren; erfordert keine Quadratwurzeln, für den Anfang :)

3
bdonlan

Hier ist eine Erweiterung des Codes von @DoctorJ um unbegrenzte Ganzzahlen basierend auf der von @nawfal angegebenen Methode. Es kann kodieren und dekodieren. Es funktioniert mit normalen Arrays und Numpy-Arrays.

#!/usr/bin/env python
from numbers import Integral    

def Tuple_to_int(tup):
    """:Return: the unique non-negative integer encoding of a Tuple of non-negative integers."""
    if len(tup) == 0:  # normally do if not tup, but doesn't work with np
        raise ValueError('Cannot encode empty Tuple')
    if len(tup) == 1:
        x = tup[0]
        if not isinstance(x, Integral):
            raise ValueError('Can only encode integers')
        return x
    Elif len(tup) == 2:
        # print("len=2")
        x, y = Tuple_to_int(tup[0:1]), Tuple_to_int(tup[1:2])  # Just to validate x and y

        X = 2 * x if x >= 0 else -2 * x - 1  # map x to positive integers
        Y = 2 * y if y >= 0 else -2 * y - 1  # map y to positive integers
        Z = (X * X + X + Y) if X >= Y else (X + Y * Y)  # encode

        # Map evens onto positives
        if (x >= 0 and y >= 0):
            return Z // 2
        Elif (x < 0 and y >= 0 and X >= Y):
            return Z // 2
        Elif (x < 0 and y < 0 and X < Y):
            return Z // 2
        # Map odds onto negative
        else:
            return (-Z - 1) // 2
    else:
        return Tuple_to_int((Tuple_to_int(tup[:2]),) + Tuple(tup[2:]))  # ***speed up Tuple(tup[2:])?***


def int_to_Tuple(num, size=2):
    """:Return: the unique Tuple of length `size` that encodes to `num`."""
    if not isinstance(num, Integral):
        raise ValueError('Can only encode integers (got {})'.format(num))
    if not isinstance(size, Integral) or size < 1:
        raise ValueError('Tuple is the wrong size ({})'.format(size))
    if size == 1:
        return (num,)
    Elif size == 2:

        # Mapping onto positive integers
        Z = -2 * num - 1 if num < 0 else 2 * num

        # Reversing Pairing
        s = isqrt(Z)
        if Z - s * s < s:
            X, Y = Z - s * s, s
        else:
            X, Y = s, Z - s * s - s

        # Undoing mappint to positive integers
        x = (X + 1) // -2 if X % 2 else X // 2  # True if X not divisible by 2
        y = (Y + 1) // -2 if Y % 2 else Y // 2  # True if Y not divisible by 2

        return x, y

    else:
        x, y = int_to_Tuple(num, 2)
        return int_to_Tuple(x, size - 1) + (y,)


def isqrt(n):
    """":Return: the largest integer x for which x * x does not exceed n."""
    # Newton's method, via http://stackoverflow.com/a/15391420
    x = n
    y = (x + 1) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x
2
NStarman

Überprüfen Sie dies: http://en.wikipedia.org/wiki/Pigeonhole_principle . Wenn A, B und C vom gleichen Typ sind, ist dies nicht möglich. Wenn A und B 16-Bit-Ganzzahlen sind und C 32-Bit ist, können Sie einfach die Verschiebung verwenden.

Die eigentliche Natur von Hash-Algorithmen besteht darin, dass sie keinen eindeutigen Hash für jede unterschiedliche Eingabe bereitstellen können.

2
Groo

Wie wäre es mit etwas viel Einfacherem: Bei zwei Zahlen sei A und B die Verkettung: 'A' + ';' + 'B'. Dann sei die Ausgabe Hash (str). Ich weiß, dass dies keine mathematische Antwort ist, aber ein einfaches Python-Skript (das über eine eingebaute Hash-Funktion verfügt) sollte die Aufgabe erfüllen.

1
Madhav Nakar

Was Sie vorschlagen, ist unmöglich. Sie werden immer Kollisionen haben.

Um zwei Objekte einem anderen Satz zuzuordnen, muss der zugewiesene Satz eine Mindestgröße der Anzahl der erwarteten Kombinationen haben:

Unter der Annahme einer 32-Bit-Ganzzahl haben Sie 2147483647 positive Ganzzahlen. Wenn Sie zwei davon auswählen, bei denen die Reihenfolge keine Rolle spielt, ergeben sich bei Wiederholung 2305843008139952128 Kombinationen. Dies passt nicht gut in den 32-Bit-Integer-Satz.

Sie können dieses Mapping jedoch in 61 Bit anpassen. Die Verwendung einer 64-Bit-Ganzzahl ist wahrscheinlich am einfachsten. Setzen Sie das High Word auf die kleinere Ganzzahl und das Low Word auf die größere.

1
lc.

Angenommen, Sie haben eine 32-Bit-Ganzzahl, warum nicht einfach A in die erste 16-Bit-Hälfte und B in die andere verschieben?

def vec_pack(vec):
    return vec[0] + vec[1] * 65536;


def vec_unpack(number):
    return [number % 65536, int(number / 65536)];

Abgesehen davon, dass dies so platzsparend wie möglich und kostengünstig zu berechnen ist, ist ein wirklich cooler Nebeneffekt, dass Sie mit der gepackten Zahl Vektor-Mathematik durchführen können.

a = vec_pack([2,4])
b = vec_pack([1,2])

print(vec_unpack(a+b)) # [3, 6] Vector addition
print(vec_unpack(a-b)) # [1, 2] Vector subtraction
print(vec_unpack(a*2)) # [4, 8] Vector multiplication

das geht durch diese beiden Punkte

0
Stuffe

lassen Sie uns zwei Zahlen B und C haben, die in eine einzelne Zahl A kodiert werden

A = B + C * N

woher

B = A% N = B

C = A/N = C

0
Ankur Chauhan

Bei positiven Zahlen A und B sei D = Anzahl der Ziffern, die A hat, und E = Anzahl der Ziffern, die B hat. Das Ergebnis kann eine Verkettung von D, 0, E, 0, A und B sein. 

Beispiel: A = 300, B = 12. D = 3, E = 2 Ergebnis = 302030012 ... Dies nutzt die Tatsache, dass die einzige Zahl, die mit 0 beginnt, 0 ist. 

Pro: Einfach zu codieren, einfach zu decodieren, von Menschen lesbar, signifikante Ziffern können zuerst verglichen werden, Vergleichspotential ohne Berechnung, einfache Fehlerprüfung.

Nachteile: Die Größe der Ergebnisse ist ein Problem. Aber das ist in Ordnung, warum speichern wir auf jeden Fall unbegrenzte Ganzzahlen in einem Computer.

0
pandanban