web-dev-qa-db-ja.com

一意かつ決定的な方法で、2つの整数を1つにマッピングする

2つの正の整数AとBを想像してください。これら2つを単一の整数Cに結合したいです。

Cに結合する他の整数DとEはあり得ません。したがって、それらを加算演算子と結合しても機能しません。例:30 + 10 = 40 = 40 + 0 = 39 + 1連結も機能しません。例:「31」+「2」= 312 =「3」+「12」

この組み合わせ演算も決定論的である必要があります(常に同じ入力で同じ結果が得られます)andは常に整数の正または負のいずれかの整数を生成する必要があります。

215
harm

全単射NxN -> Nマッピングを探しています。これらは、例えば dovetailing いわゆるペアリング関数の概要については このPDF をご覧ください。ウィキペディアには、特定のペアリング機能、つまり Cantorペアリング機能 が導入されています。

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

3つの発言:

  • 他の人が明らかにしたように、ペアリング関数の実装を計画している場合、すぐに任意の大きな整数(bignums)が必要になることがわかります。
  • ペア(a、b)と(b、a)を区別したくない場合は、ペアリング関数を適用する前にaとbをソートします。
  • 実際に私は嘘をついた。全単射ZxZ -> Nマッピングを探しています。 Cantorの関数は、負でない数でのみ機能します。しかし、これは問題ではありません。なぜなら、次のように全単射f : Z -> Nを定義するのは簡単だからです。
    • f(n)= n * 2n> = 0の場合
    • f(n)= -n * 2-1if n <0
211
Stephan202

キャンターペアリング関数 は、そのシンプルで高速かつスペース効率の高いことを考えると、実際には優れたものの1つですが、Wolframでさらに優れたものが公開されています マシューシュズジク著、こちら Cantorペアリング機能の(比較的)制限は、入力が2つのNビット整数である場合、エンコードされた結果の範囲が常に2Nビット整数の制限内に収まらないことです。つまり、私の入力が16の範囲の2つの0 to 2^16 -1ビット整数である場合、入力の2^16 * (2^16 -1)の組み合わせが可能なため、明らかな Pigeonhole Principle により、少なくともサイズの出力が必要です。 2^16 * (2^16 -1)は、2^32 - 2^16と同じです。つまり、32ビット番号のマップは理想的には実行可能です。これは、プログラミングの世界ではほとんど実用的ではないかもしれません。

キャンターペアリング機能

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

2つの最大16ビット整数(65535、65535)のマッピングは8589803520になります。これは、ご覧のように32ビットに収めることができません。

EnterSzudzikの関数

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

(65535、65535)のマッピングは4294967295になります。これは、ご覧のとおり、32ビット(0〜2 ^ 32 -1)整数です。これは、このソリューションが理想的な場所であり、単にそのスペース内のすべての単一ポイントを利用するため、より効率的なスペースは得られません。


言語/フレームワークでさまざまなサイズの番号の署名付き実装を通常処理するという事実を考慮して、-(2^15) to 2^15 -1(後で出力を拡張して符号付きにまたがる方法についてはsigned 16ビット整数を検討してみましょう範囲)。 aおよびbは正でなければならないため、0 to 2^15 - 1の範囲です。

キャンターペアリング機能

2つの最大16ビット符号付き整数(32767、32767)のマッピングは2147418112になりますが、これは符号付き32ビット整数の最大値に少し足りません。

Szudzikの関数

(32767、32767)=> 1073741823、はるかに小さい..

負の整数を考慮しましょう。それは私が知っている元の質問を超えていますが、将来の訪問者を支援するために精巧に作られています。

キャンターペアリング機能

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(Int64)。 16ビット入力の64ビット出力は、非常に許容できない場合があります。

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は、符号なし範囲では32ビット、符号付き範囲では64ビットですが、それでもなお優れています。

これはすべて、出力は常に正でした。符号付きの世界では、出力の半分を負の軸に転送できればさらにスペースを節約できます。 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

私がしていること:2の重みを入力に適用し、関数を通過した後、出力を2で除算し、それらの一部を-1で乗算して負の軸にします。

結果を参照してください。符号付き16ビット数の範囲の入力については、出力は符号付き32ビット整数の制限内に収まります。 Cantorペアリング機能について同じ方法を実行する方法はわかりませんが、効率的でないほど多くのことを試みませんでした。 さらに、Cantorペアリング機能に関係する計算が増えると、その速度も遅くなります

これが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;
}

中間計算は2N符号付き整数の制限を超える可能性があるため、4N整数型を使用しました(2による最後の除算により、結果が2Nに戻されます)。

私が代替ソリューションで提供したリンクは、空間内のすべての単一ポイントを利用する関数のグラフをうまく描いています。 一対の座標を1つの数値に可逆的に一意にエンコードできることは驚くべきことです!数字の魔法の世界!!

203
nawfal

AとBを2バイトで表現できる場合、それらを4バイトで結合できます。 Aを最上位の半分に、Bを最下位の半分に置きます。

C言語では、次のようになります(sizeof(short)= 2および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;
}
45
mouviciel

これも可能ですか?
2つの整数を組み合わせています。両方の範囲は-2,147,483,648〜2,147,483,647ですが、正の値のみを使用します。これにより、2147483647 ^ 2 = 4,61169E + 18の組み合わせになります。各組み合わせは一意である必要があり、結果として整数になるため、この数の数値を含むことができる何らかの魔法の整数が必要になります。

または、私のロジックに欠陥がありますか?

14
Boris Callens

番号aを最初、bを2番目にします。 pa+1-番目の素数、qb+1-番目の素数とする

次に、結果は、a<b,の場合はpq、または2pqの場合はa>bです。 a=bの場合、p^2とします。

8
ASk

正の整数の標準的な数学的方法は、素因数分解の一意性を使用することです。

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

欠点は、画像が非常に広い範囲の整数にまたがる傾向があるため、コンピューターアルゴリズムでマッピングを表現する場合、結果に適したタイプを選択する際に問題が発生する可能性があることです。

これを変更して、5項と7項の累乗でフラグをエンコードすることにより、負のxおよびyを処理できます。

例えば.

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

引数として正の整数を使用し、引数の順序が重要でない場合:

  1. 無秩序ペアリング関数

    <x, y> = x * y + trunc((|x - y| - 1)^2 / 4) = <y, x>
    
  2. X≠yの場合、ここに 一意の順序なしペアリング関数 があります。

    <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、ここでs(n) = n*(n+1)/2

  • これは関数であり、決定論的です。
  • また、単射です-fは異なる(a、b)ペアに異なる値をマッピングします。これを証明するには、s(a+b+1)-s(a+b) = a+b+1 < aという事実を使用します。
  • これは非常に小さな値を返します。配列を大きくする必要がないため、配列のインデックス付けに使用する場合に適しています。
  • キャッシュフレンドリーです-2つの(a、b)ペアが互いに近い場合、fは(他の方法と比較して)互いに近い番号をマップします。

あなたが何を意味するのか理解できませんでした:

整数の正または負のいずれかの側で常に整数を生成する必要があります

このフォーラムで(より大きい)、(より小さい)文字を書くにはどうすればよいですか?

4
libeako

マッピングを構築するのはそれほど難しくありません:

 1 2 3 4 5(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 (a、b)==(b、a)(鏡)
 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ネガティブ/ポジティブが必要な場合に使用
 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 

任意のa、bの値を取得する方法を理解することは、もう少し困難です。

4
Dolphin

Stephan202の答えは唯一の真に一般的な答えですが、制限された範囲の整数については、より良い結果を得ることができます。たとえば、範囲が0..10,000の場合、次のことができます。

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

結果は、整数型の基数の平方根までの範囲の単一の整数に収まります。これは、Stephan202のより一般的な方法よりもわずかに効率的にパックされます。デコードもかなり簡単です。最初に平方根を必要としません:)

3
bdonlan

@nawfalで指定されたメソッドに基づいて、@ DoctorJのコードを無制限の整数に拡張します。エンコードおよびデコードできます。通常の配列と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

これを確認してください: http://en.wikipedia.org/wiki/Pigeonhole_principle 。 A、B、Cが同じタイプの場合、実行できません。 AとBが16ビット整数で、Cが32ビットの場合、単純にシフトを使用できます。

ハッシュアルゴリズムの本質は、異なる入力ごとに一意のハッシュを提供できないことです。

2
Groo

もっと簡単な方法はどうですか:AとBの2つの数値を与えて、strを連結させます: 'A' + ';' + 'B'。次に、出力をhash(str)にします。これは数学的な答えではないことを知っていますが、簡単なpython(ハッシュ関数が組み込まれています)スクリプトが仕事をするはずです。

1
Madhav Nakar

あなたが提案することは不可能です。常に衝突が発生します。

2つのオブジェクトを別の単一のセットにマッピングするには、マッピングされたセットの予想される組み合わせの数の最小サイズが必要です。

32ビット整数を想定すると、2147483647個の正の整数があります。順序が重要ではない場所で繰り返しを使用してこれらの2つを選択すると、2305843008139952128の組み合わせが生成されます。これは、32ビット整数のセットにはうまく適合しません。

ただし、このマッピングを61ビットに収めることができます。 64ビット整数を使用するのがおそらく最も簡単です。上位のWordを小さい整数に設定し、下位のWordを大きい整数に設定します。

1
lc.

32ビットの整数があるとします。なぜAを最初の16ビットの半分に、Bをもう一方に移動しないのでしょうか。

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


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

これが可能な限りスペース効率が良く、計算が安価である以外に、本当にクールな副作用は、パックされた数値でベクトル演算を実行できることです。

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

それはこれらの2つのポイントを通過します

0
Stuffe

2つの数値BとCを持ち、それらを単一の数値Aにエンコードします

A = B + C * N

どこ

B = A%N = B

C = A/N = C

0
Ankur Chauhan

正の整数AおよびBが与えられた場合、D = Aの桁数、E = Bの桁数とします。結果は、D、0、E、0、A、およびBの連結になります。

例:A = 300、B =12。D= 3、E = 2結果=302030012。これは、0で始まる唯一の数が0であるという事実を利用しています。

プロ:エンコードが簡単、デコードが簡単、人間が読める、有効数字を最初に比較できる、計算なしで比較できる、簡単なエラーチェック.

短所:結果のサイズが問題です。しかし、それは大丈夫です、とにかくコンピューターに無制限の整数を保存するのはなぜですか。

0
pandanban