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


235

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

Cに結合する他の整数DおよびEは存在できません。そのため、これらを加算演算子と組み合わせても機能しません。例30 + 10 = 40 = 40 + 0 = 39 + 1連結も機能しません。例: "31" + "2" = 312 = "3" + "12"

この組み合わせ動作も決定論的でなければならない(常に同じ入力と同じ結果をもたらす)常に正又は整数の負側のいずれかに整数を得なければなりません。


10
ソフトウェアで整数を意味するのか、数学で整数を意味するのかを明確にする必要があります。ソフトウェアでは、整数型を選択するとサイズが決まるため、それらの数は有限であるため、解決策はありません(もちろん、入力データがある範囲内にあることが保証されていて、出力が任意の整数)。数学では、ASkのソリューションを参照してください。
ダニエルダラナス09年

私は、低い正の範囲の制限付き整数について話している。セイ0〜10,000
危害

27
@harm:では、どう10,001*A + Bですか?
BlueRaja-ダニープフルフフト

2
私はこのPHP関数を見つけました:gist.github.com/hannesl/8031402
cakan

順序が関係ない場合(例:(3,12)と(12,3)で同じ結果が出る)、「A + B」+「A * B」を使用
Sodj

回答:


233

あなたは全単射NxN -> Nマッピングを探しています。これらは、ダブテールなどに使用されます。見ていこのPDFいわゆるへの導入のためのペアリング機能を。ウィキペディアでは、特定のペアリング機能、つまりCantorペアリング機能を導入しています

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

3つの備考:

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

13
+1これは無制限の整数の正解だと思います。
不明

4
k1、k2の値を再度取得するにはどうすればよいですか?
MinuMaster 2012

3
@MinuMaster:これは同じWikipediaの記事の「Cantorペアリング関数の反転」で説明されています
Stephan202 2012

4
以下のnewfalで説明するSzudzikの関数も参照してください。
OliJG

1
これは無制限の整数には適切ですが、無制限の整数には最適ではありません。@ blue-rajaのコメントは、はるかに理にかなっていると思います。
Kardasis 2013

226

Cantorのペアリング機能は、シンプルで高速かつスペース効率が良いことを考えると、実に優れた機能の 1つですが、Wolframには、Matthew Szudzikによってさらに優れた機能が公開されています。Cantorペアリング関数の制限は、(比較的)2N入力が2 Nビット整数の場合、エンコードされた結果の範囲が常にビット整数の制限内にとどまるとは限らないことです。つまり、私の入力がから2 16ビットの整数である場合、可能な入力の組み合わせが0 to 2^16 -1存在する2^16 * (2^16 -1)ため、明らかなピジョンホール原理により、少なくとも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ビットには適合しません。

Szudzikの関数を入力します

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

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


言語/フレームワークでさまざまなサイズの数の符号付き実装を通常扱うという事実を考慮して、signed 16ビット整数を範囲として考えてみ-(2^15) to 2^15 -1ましょう(後で、符号付き範囲にまたがるように出力を拡張する方法を見ていきます)。以来abそれらは正の範囲である必要があり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ビット出力はとても許されないかもしれません!!

ズジクの機能

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つの数値に可逆的に一意にエンコードできることは驚くべきことです。数字の魔法の世界!!


5
符号付き整数の変更されたunhash関数は何でしょうか?
Arets Paeglis 2013

7
この答えは私を混乱させます。マップしたい場合は(0,0)スルー(65535,65535)、単一の番号には、a<<16 + b基本的にあらゆる方法で優れている(より速く、より簡単な、より簡単に、より明白に理解します)。あなたがしたい場合(-32768,-32768)(327687,327687)代わりに、単に対象32768最初。
BlueRaja-Danny Pflughoeft 2014年

2
@ BlueRaja-DannyPflughoeftあなたは正しいです。私の答えは、範囲が制限されていないか不明な場合に有効です。更新します。制限が気になる前に書いていた。この答えを編集することは、ずっと前から考えていました。私はいつかすぐに時間を見つけるでしょう。
nawfal

Szudzikの関数は、組み合わせまたは順列に対して機能しますか?順列のようですね?Combinationに使用したい場合、アルゴリズムのIFとElseの部分を削除できますか?
ジェイミーマーシャル

これは、任意の長さのタプルに一般化されたSzudzik関数のPython実装です:gitlab.com/snippets/32559
Doctor J

47

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

3
combine()べきreturn (unsigned short)(A<<16) | (unsigned short)(B); 負の数を適切に梱包することができますように。
Andy

2
@Andy A<<16は範囲外になります。それはする必要がありますreturn (unsigned int)(A<<16) | (unsigned short)(B);
DanSkeel 2018年

15

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

それとも私の論理に欠陥がありますか?


+1それも私が思うことです(ただし、AとBの順序は関係ないと言って計算を行いました)
lc。

4
はい、あなたの論理は鳩の穴の原理によって正しいです。残念ながら、質問者は整数が有界かどうかを指定しませんでした。
不明

はい、私も後から考えましたが、メッセージは本質的に同じだと思ったので、再計算する必要はありませんでした。
Boris Callens、

また、偶然の計算(オランダ語からの直訳)教科書をもう一度取り上げる必要があることにも気づきました。
Boris Callens、

2
@Boris:Kansrekeningは「確率論」です。
Stephan202 2009年

8

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

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

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

あなたはマイナスに対処するために、これを修正することができるxy5と7項の力でフラグを符号化することによって。

例えば

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

数学は大丈夫です。しかし、Borisが言うように、これをコンピュータープログラムとして実行する場合は、マシンの有限性を考慮する必要があります。アルゴリズムは、関連するマシンで表現可能な整数のサブセットに対して正しく機能します。
ユヴァルF

2
2番目の段落でこれを述べました。質問のタグは、特定の言語ではなく、「アルゴリズム」、「数学」、「決定論」を示しています。入力範囲は制限されない場合があり、環境には無制限の整数型「bigint」がある場合があります。
CBベイリー

8

a最初の数をb2番目の数とします。してみましょうpことa+1番目の素数、q可能b+1番目の素数

次に、結果はpq、if a<b,または2pqif a>bです。場合a=b、それはしましょうp^2


4
私はあなたがNPの解決策を望んでいることを疑っています。
user44242 2009年

1
これは、a = 5、b = 14およびa = 6、b = 15で同じ結果を生成しませんか?
Lieven Keersmaekers、2009年

3
2つの異なる素数の2つの積は同じ結果を得ることができません(一意の素因数分解)a = 5、b = 14->結果は13 * 47 = 611 a = 6、b = 15->結果は17 * 53 = 901
ASk 2009年

4

マッピングを構築するのはそれほど難しいことではありません。

   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

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は(他の方法と比較して)互いに近い数にマップします。

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

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

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


2
より大きいおよびより小さい文字は、内で正常に機能しますbacktick escapes
TRiG 2010

これはCantorペアリング関数と同等であり、負の整数では機能しません。
Davor Josipovic

4

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のより一般的な方法よりもわずかに効率的にパックされます。デコードもかなり簡単です。まず、平方根は必要ありません:)


これは、たまたまフロートで可能ですか?
Lukas


3

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

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


2

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

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


2
しかし、(8,11)と(81,1)は同じ番号811にマップされます
Leevi L

それは良い点です。中央に記号を追加するだけで、この問題を修正できます。したがって、(8、11)の場合は文字列「8-11」をハッシュし、(81、1)の場合は文字列「81-1」をハッシュします。したがって、一般的に(A、B)の場合、文字列「AB」をハッシュします。(ハックに聞こえるかもしれませんが、動作するはずです)。
Madhav Nakar

そのまた、そのタスクは、新たな整数、記号でない文字列に2つの整数をマップするために間違っているので
Leevi L

私は数学の観点からではなく、CSの観点から来ています(数学的な解決策については、上記の応答を見てください)。私は2つの整数を取得して文字列にし、その後整数に変換します。基本的に、はい、2つの整数を新しい整数にマッピングしています。
Madhav Nakar

1

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

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

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

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


1

32ビット整数があるとします。Aを最初の16ビットの半分に、Bを他の半分に移動しませんか?

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


def vec_unpack(number):
    return [number % 65536, 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] Scalar multiplication

0

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

A = B + C * N

どこ

B = A%N = B

C = A / N = C


2
この表現を一意にするためにNをどのように選択しますか?その問題を解決した場合、この答えは上記のものとどのように異なりますか?
プルーン

あなたは、Nは、両方のBとCよりも大きくなければならないことを追加する必要があります
ラドスラフ・ストヤノフ

0

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

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

プロ:エンコードが簡単で、デコードが簡単で、人が読める、有効数字を最初に比較できます。計算なしで比較できる可能性があり、単純なエラーチェックが可能です。

短所:結果のサイズが問題です。しかし、それでも問題ありません。なぜ、とにかく無限の整数をコンピューターに格納するのですか。


0

最初の数値にXビットを割り当て、2番目の数値にYビットを割り当てるなどのより詳細な制御が必要な場合は、次のコードを使用できます。

class NumsCombiner
{

    int num_a_bits_size;
    int num_b_bits_size;

    int BitsExtract(int number, int k, int p)
    {
        return (((1 << k) - 1) & (number >> (p - 1)));
    }

public:
    NumsCombiner(int num_a_bits_size, int num_b_bits_size)
    {
        this->num_a_bits_size = num_a_bits_size;
        this->num_b_bits_size = num_b_bits_size;
    }

    int StoreAB(int num_a, int num_b)
    {
        return (num_b << num_a_bits_size) | num_a;
    }

    int GetNumA(int bnum)
    {
        return BitsExtract(bnum, num_a_bits_size, 1);
    }

    int GetNumB(int bnum)
    {
        return BitsExtract(bnum, num_b_bits_size, num_a_bits_size + 1);
    }
};

合計で32ビットを使用します。ここでの考え方は、たとえば、最初の数値を最大10ビット、2番目の数値を最大12ビットにする場合、次のようにすることができます。

NumsCombiner nums_mapper(10/*bits for first number*/, 12/*bits for second number*/);

今、あなたはに格納できるnum_a最大数2^10 - 1 = 1023と中num_bのnaximum値2^12 - 1 = 4095

num Aとnum Bの値を設定するには:

int bnum = nums_mapper.StoreAB(10/*value for a*/, 12 /*value from b*/);

これbnumですべてのビットです(合計32ビット。64ビットを使用するようにコードを変更できます)。

int a = nums_mapper.GetNumA(bnum);

num bを取得するには:

int b = nums_mapper.GetNumB(bnum);

編集: bnumクラス内に保存できます。私は自分のニーズに合わせてコードを共有し、それが役立つことを期待して、それをしませんでした。

ソースをありがとう:https : //www.geeksforgeeks.org/extract-k-bits-given-position-number/ ビットを抽出する関数とmouviciel、この投稿での回答にも感謝します。これらをソースに使用して、より高度なソリューションを見つけることができました

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.