全単射関数ℤ→ℤⁿ


23

(すべての整数のセット)から(たとえば恒等関数)に全単射関数を作成するのは簡単です。ZZZ

それから全単射関数を作成することも可能であるに(2つの整数のすべてのペアのセットのデカルト積のと)。たとえば、2D平面上の整数ポイントを表すラティスを取得し、0から外側にらせんを描き、そのポイントと交差するときのらせんに沿った距離として整数のペアをエンコードできます。Z 2 Z ZZZ2ZZ

スパイラル

(自然数でこれを行う関数は、ペアリング関数として知られています。)

実際、これらの全単射関数のファミリーが存在します:

fkバツZZk

チャレンジ

関数(は正の整数)のファミリーを、整数を個の整数のタプルに全単射でマッピングするというプロパティで定義します。k f kx kfkバツkfkバツk

入力と与えられると、提出は返す必要があります。x f kx kバツfkバツ

これはなので、最短の有効な回答(バイト単位で測定)が勝ちます。

仕様書

  • 上記の基準を満たす限り、任意のファミリを使用できます。fkバツ
  • 関数ファミリーの動作方法の説明と、関数の逆数を計算するスニペットを追加することをお勧めします(これはバイトカウントには含まれません)。
  • 関数が全単射であることを証明できる限り、逆関数が計算不可能であれば問題ありません。
  • 言語の符号付き整数と符号付き整数のリストに適切な表現を使用できますが、関数への入力を無制限にする必要があります。
  • 値を最大127までサポートする必要があります。k

それは、文字列のバージョン取ることも大丈夫ですkし、x代わりに整数を?
ジョンファンミン

@JungHwanMin入力数値を表す文字列は問題ありません。
エソランジングフルーツ

回答:


19

アリス14 12バイト

/O
\i@/t&Yd&

オンラインでお試しください!

逆関数(ゴルフではない):

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

オンラインでお試しください!

説明

アリスは、内蔵している間の全単射2で計算することができる、Y(アンパック)およびその逆Z (パック)。バイジェクションを説明するドキュメントからの抜粋を次に示します。

バイジェクションの詳細は、ほとんどのユースケースには無関係である可能性があります。主なポイントは、ユーザーが2つの整数を1つにエンコードし、後で2つの整数を再度抽出できるようにすることです。packコマンドを繰り返し適用することにより、整数のリストまたはツリー全体を1つの数値に格納できます(ただし、特にメモリ効率のよい方法ではありません)。パック操作によって計算マッピングは、全単射関数である2 →ℤ(すなわち、1対1のマッピング)。まず、整数{...、-2、-1、0、1、2、...}のような(ゼロを含む自然数)にマッピングされ、...、{3、1、0、2 4 、...}(つまり、負の整数は奇数の自然にマップされ、負でない整数は偶数の自然にマップされます)。2つの自然数は、カントールペアリング関数を介して1つにマッピングされます。この関数は、整数グリッドの最初の象限の対角線に沿って自然数を書き込みます。具体的には、{(0,0)、(1,0)、(0,1)、(2,0)、(1,1)、(0,2)、(3,0)、...}であります{0、1、2、3、4、5、6、...}にマッピングされます。結果の自然数は、以前の全単射の逆を使用して整数にマップされます。unpackコマンドは、このマッピングの逆を正確に計算します。

上記で触れたように、私たちは、マップするために、このアンパック操作を使用することができますℤをするℤのKとしても。これを初期整数に適用した後、結果の2番目の整数を再びアンパックできます。これにより、3つの整数のリストが得られます。したがって、k-1個のアプリケーションは、結果としてk個の整数をY与えます。

リストをZ最後からパックして逆数を計算できます。

したがって、プログラム自体には次の構造があります。

/O
\i@/...d&

これは、可変数の10進整数を入力として読み取り、結果として可変数を出力するプログラムの単なる基本テンプレートです。したがって、実際のコードは実際には次のようになります。

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

私はアドレスしたいことの一つは、「なぜだろうアリスが内蔵されているためされℤ→ℤ 2全単射、言語の領土をゴルフということではありませんか」?アリスの奇妙な組み込み機能のほとんどと同じように、主な理由は、アリスの設計原理は、すべてのコマンドがしなければならない二つの意味、枢機卿(整数)モード用と序(文字列)モードのための1つ、およびこれら二つの意味があるということです何とか与えることに関連しますカーディナルモードとオーディナルモードは、それらが物事が同じだけでなく異なるものであるミラー宇宙であるという感覚。そして、かなり頻繁に、追加したい2つのモードの1つに対するコマンドがあり、それとペアにする他のコマンドを把握する必要がありました。

以下の場合YZ序モード最初に来た:私は2つの文字列(ジッパー)をインタリーブし、再び(解凍)をそれらを分離する機能を持っていると思いました。カーディナルモードでキャプチャしたかったのは、2つの整数を1つ作成し、後で2つの整数を再び抽出できるようにすることでした。これにより、このような全単射が自然な選択になります。

また、これはゴルフの外で実際に非常に役立つと考えました。なぜなら、リスト全体または整数のツリーさえもメモリの単一ユニット(スタック要素、テープセルまたはグリッドセル)に保存できるからです。


いつものように素晴らしい説明
ルイスメンドー

アリスのドキュメントを見つけYZ、実際にこのチャレンジを投稿するきっかけになりました(私はしばらくの間それについて考えていましたが、これは私に思い出させました)。
エソランジングフルーツ

11

Python、96 93バイト

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

これは原則として、入力数x平衡3進数に変換し、次にラウンドロビン方式で異なる座標間で最下位のトリット(3進数)を最初に分配することによって機能します。したがって、k=2たとえば、偶数の位置にあるすべてのトリットがx座標に寄与し、奇数に位置するすべてのトリットがy座標に寄与します。というのk=3は、1番目、4番目、7番目のトリット(など)がに貢献しx、2番目、5番目、8番目がに貢献しy、3番目、6番目、9番目がに貢献するからzです。

たとえばk=2、では、を見てみましょうx=35。バランスのとれた三項で35110T、ウィキペディアの記事の表記を使用しT-1数字を表します。トリットを分割する1Tと、x座標10(右から数えて最初と3 番目のトリット)と座標(2番目と4番目のトリット)が得られyます。各座標を10進数に変換し直します2, 3

もちろん、実際にゴルフのコードで整数をバランスの取れた3進数に変換するわけではありません。一度に1つのトリットを(v変数で)計算し、その値を適切な座標に直接追加しています。

以下は、座標のリストを取得して数値を返す、機能を持たない逆関数です。

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

私のf機能はおそらくそのパフォーマンスで注目に値します。O(k)メモリのみを使用しO(k) + O(log(x))、結果を見つけるのに時間がかかるため、非常に大きな入力値を処理できます。試してみてくださいf(10000, 10**10000)(そう指数に余分なゼロを追加するたとえば、あなたはかなりすぐに答えを得るでしょうx10**100000、それは私の古いPC上で30秒ほどをとります)。逆関数はそれほど速くはありません。ほとんどの場合、完了したかどうかを判断するのが難しいためです(変更のたびにすべての座標をスキャンするので、時間がかかりますO(k*log(x)))。おそらくより高速になるように最適化できますが、おそらく通常のパラメーターに対してはすでに十分に高速です。


あなたは、スペース(改行)を削除することができ内部の whileループ
氏Xcoder

おかげで、ループと;、単一行でステートメントをチェーン化することとの間に何らかの矛盾があると誤って考えていました。
Blckknght

9

、10バイト

§~!oΠR€Θݱ

オンラインでお試しください!

逆関数も10バイトです。

§o!ȯ€ΠRΘݱ

オンラインでお試しください!

説明

順方向:

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

逆方向:

§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

ビルトインのデカルト積Πは、無限リストに対してうまく動作し、各kタプルを1回だけ列挙します。


[[0,1,-1,..],[[0,1,-1,..],[[0,1,-1,..]]この部分は[[0,1,-1,..],[0,1,-1,..],[0,1,-1,..]]どうなっているの?
エリックアウトゴルファー

@EriktheOutgolferうーん、今すぐ修正。
ズガーブ

これは美しいです。Jプログラマーとして、このような遅延リストソリューションをJに変換する良い方法があるかどうか知っていますか? ^:^:_タイプのソリューションは通常、はるかに面倒です
ジョナ

@ジョナ私はわからない。からのエントリを持つすべてのkタプルの配列を計算し、i: x絶対値の合計で並べ替えてから、その中にインデックスを付けることができます。これは、これらの配列がすべてのkタプルを含む1つの「無限配列」のプレフィックスであるという考え方です。
-Zgarb

7

Wolfram言語(Mathematica)、61バイト

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

オンラインでお試しください!

(整数を取得し、次にタプルの長さを入力として取得します。)

逆:

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

オンラインでお試しください!

使い方

アイデアは簡単です:整数入力を正の整数に変換します(0,1,2,3、...を1,3,5,7、...と-1、-2、-3にマッピングすることにより、 ...から2,4,6、...)そして、すべてのkタプルにインデックスを付け、原点からの距離、そしてMathematicaのデフォルトのタイブレークによってソートします。

しかし、我々は我々が探しているそう際に、無限のリストを使用することはできませんのn 番目 のkタプル、我々は唯一の生成のkタプルの範囲内の整数の{ - N、...、N }。これは、十分であることが保証されているN 番目の最小のkのノルムによってタプル未満ノルムを有し、N、およびノルムのすべてのタプルは、n個以下、このリストに含まれています。

逆の場合は、十分な長さのkタプルのリストを生成し、そのリスト内の指定されたkタプルの位置を見つけてから、「fold in a positive integer」演算を反転します。


2
入力して実行すると[15, 5]...私のPCがクラッシュ
JungHwan分

2
それが起こります。原則として、アルゴリズムは何でも機能しますが、あなたのケースでは、範囲{-31、..、31}からすべての5タプルを生成し、31番目のものを取得することで機能するため、かなりメモリを消費します。
ミシャラヴロフ

3

J、7バイト

#.,|:#:

これを行うJコードは非常に単純です

非常に単純なペアリング関数(またはタプル関数)は、各数値のバイナリ展開の桁を単純にインターリーブすることです。したがって、たとえば次の(47, 79)ようにペアリングされます。

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

または、6399。明らかに、任意のnタプルに簡単に一般化できます。

これが動詞ごとにどのように機能するかを調べてみましょう。

#:反底2です。単項で使用すると、数値のバイナリ展開を返します。#: 47 79結果を与える:

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

|:は、単に配列を回転させる転置演算子です。の結果を回転すると#: 47 79

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

単項で使用される場合、ravel ,演算子は、テーブルから1次元のリストを生成します。

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

最後に#.、バイナリ展開を元に戻し、結果を返し6339ます。

このソリューションは、整数の任意の文字列に対して機能します。


7
これは負の数に対してどのように機能しますか?
ニール

2

Perl 6、148バイト

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

オンラインでお試しください!

ゴルフをしていない:

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

説明:

  • rect($n)は、座標の(-$n,$n)からの矩形のエッジ上の積分点の座標を生成するヘルパー関数です($n, $n)

  • @spiral 0から始まる、サイズが増加する長方形のエッジ上の積分ポイントのレイジーな無限リストです。

  • f($k)整数から整数の$kタプルまでの全単射である関数を返します。

場合$k1fIDマッピングを返します-> $_ { $_ }

それ以外の場合&gは、整数から整数の$k-1タプルへの再帰的に取得されたマッピングです。

次に、@spiral原点から出て、各点で$kX座標とgY座標を使用した呼び出しのフラット化された結果を取得することにより、-タプルを形成します。この遅延生成マッピングは、配列に格納され@vます。

@v$kインデックス0で始まるすべての- タプルが含まれているため、インデックスを負の整数に拡張するには、正の入力を偶数に、負の入力を奇数にマッピングするだけです。@vこの方法で要素を検索する関数(クロージャ)が返されます。


2

JavaScript、155バイト

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)

Prettifyバージョン:

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;
  });
}
  • まず、すべての整数をすべての非負の整数に1つずつマッピングします。
    • n> 0の場合、結果= n * 2
    • それ以外の場合、結果= -n * 2-1
  • 次に、k長の非負整数を持つすべてのタプルに順序を付けます:
    • すべての要素の合計を計算し、小さい方が最初に来る
    • 合計が等しい場合、左から右に比較して、小さい方が最初に来る
    • その結果、すべての非負整数からk個の非負整数を持つタプルへのマップが得られました
  • 最後に、2番目のステップで与えられたタプルの非負整数を、最初のステップで同様の式を持つすべての整数にマッピングします

x<0?~x-x:x+x2バイト節約できると思います。
ニール

2

Wolfram言語(Mathematica)、107バイト

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

オンラインでお試しください!

逆、60バイト

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

オンラインでお試しください!

説明:

Z-> N0経由 f(n) = 2n if n>=0 and -2n-1 if n<0

ペアリング関数の逆を介したN0-> N0 ^ 2

N0-> N0 ^ k長さを得るまで、左端の数に上記を繰り返し適用します k

N0 ^ k-> Z ^ k via f(n) = (-1)^n * ceil(n/2)、要素単位


Mathematica、101バイト

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

上記に似ています(N0の代わりにNを使用します)が、全単射fの逆を使用します:N ^ 2-> N via f(a, b) = 2^(a - 1)(2b - 1)


つまり...そのための組み込みのMathematicaはありません(アリスが持っている場合)?言葉が出ません。
JayCe

1

JavaScript、112バイト

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. 非負に変換する
  2. (n * k + i)番目の数字からi番目の数字
  3. 戻す

@HermanLauensteinは元に戻す必要はありませんか?
tsh

x<0?~x-x:x+x2バイト節約できると思います。
ニール

-5バイトを使用[...BT${x<0?~x-x:x+x}BT].reverse().map((c,i)=>r[i%k]+=c),(@Neilのクレジットx<0?~x-x:x+x)。.reverse()代わりに使用されます、(s.length-i)なぜならそれはs最初の余分なパラメータの必要性を避けるから.mapです。一時配列は再び使用されないため、逆戻りする必要はありません。(私はそれをテストしていませんが、おそらく動作するはずです)
ハーマンL

別のバイトを交換することによって保存することが可能.fill('').fill(0)先行ゼロが(少なくともSafariでテストされていない場合)違いはないので、
ハーマンLを

@HermanLauenstein試しました.fill`` か?さらに数バイト節約できます。
ニール


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