web-dev-qa-db-fra.com

Mappage de deux entiers sur un, de manière unique et déterministe

Imaginez deux entiers positifs A et B. Je veux combiner ces deux en un seul entier C. 

Il ne peut y avoir aucun autre nombre entier D et E qui se combinent en C . Donc, les combiner avec l'opérateur d'addition ne fonctionne pas. Par exemple, 30 + 10 = 40 = 40 + 0 = 39 + 1 La concaturation ne fonctionne pas non plus. Par exemple, "31" + "2" = 312 = "3" + "12"

Cette opération de combinaison doit également être déterministe (toujours produire le même résultat avec les mêmes entrées) et doit toujours générer un entier, du côté positif ou du côté négatif des entiers.

207
harm

Vous recherchez une correspondance bijective NxN -> N. Ceux-ci sont utilisés pour, par exemple. en harmonie . Regardez this PDF pour une introduction aux fonctions dites de couplage. Wikipedia introduit une fonction de couplage spécifique, à savoir la fonction de couplage Cantor :

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

Trois remarques:

  • Comme d'autres l'ont clairement indiqué, si vous envisagez d'implémenter une fonction d'appariement, vous pourriez bientôt avoir besoin de nombres entiers arbitrairement grands (bignums).
  • Si vous ne voulez pas faire de distinction entre les paires (a, b) et (b, a), triez a et b avant d'appliquer la fonction d'appariement.
  • En fait j'ai menti. Vous recherchez une correspondance bijective ZxZ -> N. La fonction de Cantor ne fonctionne que sur les nombres non négatifs. Ce n’est pas un problème cependant, car il est facile de définir un f : Z -> N de bijection, comme ceci:
    • f(n) = n * 2 si n> = 0
    • f(n) = -n * 2 - 1 si n <0
206
Stephan202

La fonction de couplage Cantor est vraiment l’un des meilleurs du point de vue de la simplicité, de la rapidité et de l’espace disponible, mais il y a quelque chose de mieux publié chez Wolfram de Matthew Szudzik, ici . La limite (relative) de la fonction d'appariement de Cantor réside dans le fait que la plage des résultats codés ne reste pas toujours dans les limites d'un nombre entier 2N bits si les entrées sont deux nombres entiers N bits. C'est-à-dire que si mes entrées sont deux nombres entiers de 16 bits allant de 0 to 2^16 -1, il y a des combinaisons d'entrées 2^16 * (2^16 -1) possibles, donc, par le principe évident de Pigeonhole , nous avons besoin d'une sortie de taille au moins 2^16 * (2^16 -1), égale à 2^32 - 2^16. ou, en d’autres termes, une mappe de nombres de bits 32 devrait être réalisable dans l’idéal. Cela n’a peut-être pas une faible importance pratique dans le monde de la programmation. 

Fonction de couplage Cantor:

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

Le mappage pour deux entiers maximum 16 bits (65535, 65535) sera 8589803520 qui, comme vous le voyez, ne peut pas être inséré dans 32 bits.

Entrez fonction de Szudzik

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

Le mappage pour (65535, 65535) sera désormais 4294967295, ce qui, comme vous le voyez, est un entier de 32 bits (0 à 2 ^ 32 -1). C’est là que cette solution est idéale: elle utilise simplement chaque point de cet espace, pour que rien ne soit plus efficace en termes d’espace. 


Maintenant, considérant le fait que nous traitons généralement avec les implémentations signées de nombres de tailles différentes dans des langages/frameworks, considérons des entiers de bits signed 16 allant de -(2^15) to 2^15 -1 (nous verrons plus loin comment étendre même la sortie sur une plage signée). Puisque a et b doivent être positifs, ils vont de 0 to 2^15 - 1.

Fonction de couplage Cantor

Le mappage pour deux entiers signés au plus 16 bits (32767, 32767) au maximum sera 2147418112, ce qui est juste en-deçà de la valeur maximum pour un entier signé de 32 bits.

Maintenant, fonction de Szudzik

(32767, 32767) => 1073741823, beaucoup plus petit.

Comptons les entiers négatifs. C'est au-delà de la question initiale que je connais, mais j'élabore juste pour aider les futurs visiteurs.

Fonction de couplage Cantor:

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 qui est Int64. Une sortie 64 bits pour des entrées 16 bits peut être si impardonnable !!

_ {Fonction de Szudzik} _: 

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 qui correspond à 32 bits pour la plage non signée ou à 64 bits pour la plage signée, mais toujours mieux.

Maintenant tout cela alors que la sortie a toujours été positive. En mode signé, cela permettra un gain de place supplémentaire si nous pouvions transférer la moitié de la sortie sur l’axe négatif. Vous pouvez le faire comme ceci pour Szudzik:

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

Ce que je fais: Après avoir appliqué un poids de 2 aux entrées et parcouru la fonction, je divise ensuite la sortie par deux et en amène certaines d’entre elles à l’axe négatif en les multipliant par -1

Consultez les résultats. Pour toute entrée comprise dans la plage d’un numéro de bit 16 signé, la sortie se situe dans les limites d’un entier binaire signé 32 qui est cool. Je ne suis pas sûr de savoir comment procéder de la même manière pour la fonction de couplage Cantor mais je n'ai pas essayé autant que ce n'est pas aussi efficace. De plus, plus de calculs sont impliqués dans la fonction d’appariement de Cantor, ce qui signifie aussi sa lenteur 

Voici une implémentation en C #.

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

Puisque les calculs intermédiaires peuvent dépasser les limites de 2N entier signé, j'ai utilisé le type entier 4N (la dernière division par 2 ramène le résultat à 2N).

Le lien que j'ai fourni sur la solution de rechange décrit joliment un graphique de la fonction utilisant chaque point de l'espace. _ {C'est étonnant de voir que vous pouvez encoder de manière unique une paire de coordonnées en un seul nombre de manière réversible! Le monde magique des nombres !!

194
nawfal

Si A et B peuvent être exprimés avec 2 octets, vous pouvez les combiner sur 4 octets. Mettez A sur la moitié la plus significative et B sur la moitié la moins significative.

En langage C, ceci donne (en supposant que sizeof (short) = 2 et 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

Est-ce seulement possible?
Vous combinez deux entiers. Ils ont tous les deux une plage allant de -2 147 483 648 à 2 147 483 647, mais vous ne prenez que les éléments positifs . Cela fait 2147483647 ^ 2 = 4,61169E + 18 combinaisons . Chaque combinaison devant être unique ET donner un nombre entier, Il vous faudra une sorte d’entier magique pouvant contenir autant de nombres. 

Ou ma logique est-elle défectueuse?

14
Boris Callens

Soit le nombre a le premier, b le second. Soit p le a+1- e nombre premier, q le b+1- e nombre premier

Ensuite, le résultat est pq, si a<b, ou 2pq si a>b. Si a=b, laissez-le être p^2.

8
ASk

La méthode mathématique standard pour les entiers positifs consiste à utiliser l'unicité de la factorisation en facteurs premiers.

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

L'inconvénient est que l'image a tendance à s'étendre sur un assez grand nombre d'entiers. Ainsi, lorsqu'il s'agit d'exprimer le mappage dans un algorithme informatique, vous pouvez avoir des difficultés à choisir un type approprié pour le résultat.

Vous pouvez modifier ceci pour traiter avec les variables x et y négatives en codant des indicateurs de puissances de 5 et 7 termes.

par exemple.

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

Pour les entiers positifs comme arguments et où l'ordre des arguments n'a pas d'importance:

  1. Voici une fonction de couplage non ordonnée }:

    <x, y> = x * y + trunc((|x - y| - 1)^2 / 4) = <y, x>
    
  2. Pour x ≠ y, voici un fonction unique d'appariement non ordonné }:

    <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, où s(n) = n*(n+1)/2

  • C'est une fonction - c'est déterministe. 
  • Il est également injectif - f mappe différentes valeurs pour différentes paires (a, b). Vous pouvez prouver cela en utilisant le fait: s(a+b+1)-s(a+b) = a+b+1 < a
  • Il retourne des valeurs assez petites - bien si vous allez l'utiliser pour l'indexation de tableau, car le tableau n'a pas besoin d'être grand. 
  • Il est convivial en cache - si deux paires (a, b) sont proches l'une de l'autre, f leur affecte des nombres proches (par rapport à d'autres méthodes).

Je n'ai pas compris ce que tu veux dire par: 

devrait toujours donner un entier sur soit le positif ou le négatif côté des entiers

Comment puis-je écrire (plus grand que), (moins que) des personnages dans ce forum?

4
libeako

Ce n'est pas si difficile de construire une cartographie:

 1 2 3 4 5 utilisez cette correspondance si (a, b)! = (B, a) 
 1 0 1 3 6 10 
 2 2 4 7 11 16 
 3 5 8 12 17 23 
4 9 13 18 24 31 
 5 14 19 25 32 40 

 1 2 3 4 5 utilise cette correspondance si (a, b) == (b, a) (miroir) 
 1 0 1 2 4 6 
 2 1 3 5 7 10 
 3 2 5 8 11 14 
 4 4 8 11 15 19 
 5 6 10 14 19 24 


 0 1 -1 2 -2 à utiliser si vous avez besoin de négatif/positif 
 0 0 1 2 4 6 
 1 1 3 5 7 10 
- 1 2 5 8 11 11 
 2 4 8 11 15 19 
- 2 6 10 10 14 19 24 

Il est un peu plus difficile de déterminer comment obtenir la valeur d'un a, b arbitraire.

4
Dolphin

Bien que la réponse de Stephan202 soit la seule vraiment générale, vous pouvez faire mieux pour les entiers dans une plage délimitée. Par exemple, si votre plage est comprise entre 0 et 10 000, vous pouvez procéder comme suit:

#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));
}

Les résultats peuvent tenir dans un seul entier pour une plage allant jusqu'à la racine carrée de la cardinalité du type entier. Cela compense légèrement plus efficacement que la méthode plus générale de Stephan202. Il est également beaucoup plus simple à décoder. ne nécessitant pas de racines carrées, pour commencer :)

3
bdonlan

Voici une extension du code de @DoctorJ aux entiers non liés basée sur la méthode donnée par @nawfal. Il peut encoder et décoder. Cela fonctionne avec les tableaux normaux et les tableaux numpy.

#!/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

Vérifiez ceci: http://en.wikipedia.org/wiki/Pigeonhole_principle . Si A, B et C sont du même type, cela ne peut être fait. Si A et B sont des entiers sur 16 bits et que C est sur 32 bits, vous pouvez simplement utiliser le décalage.

La nature même des algorithmes de hachage est qu'ils ne peuvent pas fournir un hachage unique pour chaque entrée différente.

2
Groo

Que diriez-vous de quelque chose de beaucoup plus simple: Soit deux nombres, A et B, donnons à str la concaténation: 'A' + ';' + 'B'. Ensuite, laissez la sortie être hash (str). Je sais que ce n'est pas une réponse mathématique, mais un script simple en python (qui a une fonction de hachage intégrée) devrait faire l'affaire.

1
Madhav Nakar

Ce que vous suggérez est impossible. Vous aurez toujours des collisions.

Pour mapper deux objets sur un autre jeu unique, le jeu mappé doit avoir une taille minimale du nombre de combinaisons attendues:

En supposant un entier de 32 bits, vous avez 2147483647 entiers positifs. Choisir deux de ces options pour lesquelles l'ordre n'a pas d'importance et avec répétition, permet d'obtenir 2305843438139952128 combinaisons. Cela ne s’intègre pas bien dans l’ensemble des entiers 32 bits.

Vous pouvez toutefois adapter ce mappage en 61 bits. L'utilisation d'un entier de 64 bits est probablement la plus simple. Définissez le mot haut sur le plus petit entier et le mot bas sur le plus grand.

1
lc.

Disons que vous avez un entier de 32 bits, pourquoi ne pas simplement déplacer A dans la première moitié de 16 bits et B dans l’autre?

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


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

Autre que cela étant aussi peu encombrant que possible et peu coûteux à calculer, un effet secondaire vraiment intéressant est que vous pouvez effectuer des calculs vectoriels sur le nombre condensé.

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

qui passe par ces deux points

0
Stuffe

laissez-nous deux numéros B et C, les codant en un seul nombre A

A = B + C * N

B = A% N = B

C = A/N = C

0
Ankur Chauhan

Étant donné les entiers positifs A et B, soit D = nombre de chiffres que A a et E = nombre de chiffres B a Le résultat peut être une concaténation de D, 0, E, 0, A et B. 

Exemple: A = 300, B = 12. D = 3, E = 2 résultat = 302030012. Ceci tire parti du fait que le seul nombre commençant par 0 est 0, 

Pro: facile à encoder, facile à décoder, lisible par l'homme, les chiffres significatifs peuvent être comparés en premier lieu, potentiel de comparaison sans calcul, simple vérification des erreurs.

Inconvénients: La taille des résultats est un problème. Mais bon, pourquoi stockons-nous des entiers non liés dans un ordinateur de toute façon?.

0
pandanban