双射函数ℤ→ℤⁿ


23

从(所有整数的集合)到(例如,恒等函数)创建双射函数是很简单的。žžž

也可以创建从到(所有2对整数的集合;和的笛卡尔积)的双射函数。例如,我们可以使用表示2D平面上整数点的晶格,从0向外绘制螺旋,然后将整数对编码为与该点相交时沿螺旋的距离。Z 2 Z Zžž2žž

螺旋

(使用自然数执行此操作的功能称为配对功能。)

实际上,存在以下一系列双射函数:

FķXžžķ

挑战

使用双射将整数映射到个整数元组的属性,定义函数(其中是一个正整数)。k f kx kFķXķFķXķ

给定输入和,您的提交应返回。x f kx ķXFķX

这是,因此最短的有效答案(以字节为单位)获胜。

技术指标

  • 只要满足上述条件,就可以使用任何家族。FķX
  • 鼓励您添加有关函数系列工作方式的描述,以及用于计算函数反函数的代码段(这不包括在字节数中)。
  • 如果反函数不可计算就可以,只要您可以证明它是双射的即可。
  • 您可以对语言的带符号整数和带符号整数列表使用任何合适的表示形式,但是必须允许对函数的输入是无界的。
  • 您只需要支持最大127 的值。ķ

可以使用字符串形式的kx代替整数吗?
JungHwan Min

@JungHwanMin代表输入数字的字符串很好。
硕果累累

Answers:


19

爱丽丝14 12字节

/O
\i@/t&Yd&

在线尝试!

反函数(不打高尔夫球):

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

在线尝试!

说明

Alice有一个内置的双射之间2,其可以与被计算Y(解压缩)及其逆Z (包)。这是摘录自文档的摘录:

对于大多数用例,双射的细节可能无关紧要。要点是,它允许用户将两个整数编码为一个,然后再提取两个整数。通过重复应用pack命令,整个列表或整数树可以以单个数字存储(尽管不是以特别节省内存的方式)。由压缩操作计算的映射是双射函数function 2 →ℤ(即一对一映射)。首先,将整数{...,-2,-1、0、1、2,...}映射到自然数(包括零),例如{...,3、1、0、2、4 ,...}(换句话说,负整数映射到奇数自然数,非负整数映射到偶数自然数)。然后,通过Cantor配对函数将这两个自然数映射到一个自然数,该函数沿整数网格的第一象限的对角线写入自然数。具体而言,{(0,0),(1,0),(0,1),(2,0),(1,1),(0,2),(3,0),...}是映射到{0,1,2,3,4,5,6,...} 。然后,使用较早的双射反函数将所得自然数映射回整数。unpack命令精确计算此映射的逆函数。

如上面提到的,我们可以使用该解压缩操作以映射ķ为好。将其应用于初始整数后,我们可以再次解压缩结果的第二个整数,这将为我们提供三个整数的列表。因此,K-1的应用Y给我们ķ整数作为结果。

我们可以通过将列表Z从头开始打包起来来计算逆。

因此,程序本身具有以下结构:

/O
\i@/...d&

这只是程序的基本模板,该程序读取可变数量的十进制整数作为输入,并输出可变数量的结果。所以实际的代码实际上只是:

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

有一两件事我想解决的是“为什么会爱丽丝有一个内置的ℤ→ℤ 2双射,这不就是打高尔夫球语言领土”?与Alice的大多数怪异内置程序一样,主要原因是Alice的设计原则,即每个命令都有两种含义,一种是Cardinal(整数)模式,一种是Ordinal(字符串)模式,这两种含义应该以某种方式相关联基数和序数模式使人感觉它们是镜像的宇宙,事物在某种程度上是相同的,但也有所不同。通常,我有一个要添加的两种模式之一的命令,然后不得不找出与之配对的其他命令。

Yand ZOrdinal模式的情况下首先出现:我想要一个函数来交织两个字符串(zip)并再次分开(解压缩)。我想在基数模式下捕获的质量是从两个中形成一个整数,以后又可以再次提取两个整数,这使得双射成为自然选择。

我还认为这在打高尔夫球之外实际上非常有用,因为它使您可以在单个内存单元(堆栈元素,磁带单元或网格单元)中存储整个列表或整数树。


一如既往的
精彩

实际上,在Alice文档中找到YZ促使我发布了这个挑战(我已经考虑了一段时间,但这使我想起了)。
Esolanging Fruit

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平衡三进制,然后首先以循环方式在不同坐标之间分配最低有效位的三叉戟(三进制数字)来进行的。因此,k=2例如,每个偶数定位的Trit将有助于x坐标,而每个奇数定位的Trit将有助于y坐标。因为k=3您将拥有第一,第四和第七三叉戟(etc ...)x,而第二,第五和第八则为y,第三,第六和第九而已z

例如,使用k=2,让我们看一下x=35。在平衡三进制中,35110T(使用Wikipedia文章的符号T表示-1数字)。将三叉戟除以给出1T(第一个和第三个三叉戟,从右边开始计数)作为x坐标,并给出10(第二和第四个三叉戟)作为y坐标。将每个坐标转换回十进制,我们得到2, 3

当然,我实际上并没有在高尔夫球代码中一次将整数转换为平衡三进制。我只是一次(在v变量中)计算一个Trit ,然后将其值直接添加到适当的坐标中。

这是一个无轮廓的逆函数,它获取一个坐标列表并返回一个数字:

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使得需要30秒左右我的旧电脑上)。逆函数不是那么快,主要是因为很难告诉它何时完成(它在每次更改后都会扫描所有坐标,因此需要花费一些O(k*log(x))时间)。可能已对其进行了优化,使其速度更快,但对于常规参数而言,它可能已经足够快了。


您可以删除while循环的空格(换行符)
Xcoder先生,2017年

谢谢,我错误地认为循环与;用于在单行上链接语句之间存在某种冲突。
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元组恰好枚举一次。


[[0,1,-1,..],[[0,1,-1,..],[[0,1,-1,..]]这部分应该是[[0,1,-1,..],[0,1,-1,..],[0,1,-1,..]]吗?
暴民埃里克(Erik the Outgolfer)'17年

@EriktheOutgolfer恩,是的,现在修复。
Zgarb

这很漂亮。作为一名J程序员,您是否知道是否有一种很好的方法可以将像这样的惰性列表解决方案转换为不支持它们的J? ^:^:_类型的解决方案通常要麻烦得多...
乔纳

@乔纳我不确定。您可以尝试计算所有k个元组的数组,其中包含from的条目,i: x并按绝对值的总和对其进行排序,然后对其进行索引。想法是这些数组是一个包含所有k个元组的“无限数组”的前缀。
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元组时,我们只生成{ -n,...,n } 范围内的整数k元组。这保证足够了,因为按规范n 最小的k元组具有小于n的范数,并且所有规范n为或小于n的元组都包含在此列表中。

对于反函数,我们只生成足够长的k个元组列表,找到给定k个元组在该列表中的位置,然后将“对折为正整数”运算。


2
输入运行使[15, 5]我的PC崩溃了
JungHwan Min

2
那会发生的。原则上,该算法适用于任何事物,但在您的情况下,它可以通过生成{-31,..,31}范围内的所有5元组然后取第31个元组来起作用,因此它占用大量内存。
Misha Lavrov

3

J,7个字节

#.,|:#:

用J代码做到这一点非常简单

一个非常简单的配对功能(或联结功能)是简单地交错每个数字的二进制扩展数字。因此,例如(47, 79)将这样配对:

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

或6399。显然,我们可以简单地推广到任何n元组。

让我们逐个检查动词的工作方式。

#:是反基二,当单数使用时,它返回数字的二进制扩展。#: 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 运算符,它从表中生成一维列表:

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-tuples的双射。

如果$k1,则f返回身份映射-> $_ { $_ }

否则,&g是从整数到整数$k-1-tuples 的递归获得的映射。

然后,我们@spiral从原点移出,并在每个点$k通过获取X坐标和g使用Y坐标进行调用的展平结果形成一个-元组。延迟生成的映射存储在数组中@v

@v包含$k从索引0开始的所有-tuple,因此要将索引扩展到负整数,我们只将正输入映射到偶数,将负输入映射到奇数。返回一个函数(关闭),@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)

美化版本:

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;
  });
}
  • 首先,我们将所有整数一一映射到所有非负整数:
    • 如果n> 0,则结​​果= n * 2
    • 否则结果= -n * 2-1
  • 其次,我们给所有k长度非负整数的元组定序:
    • 计算所有元素的总和,较小者排在前
    • 如果总和相等,则从左至右比较,较小的优先
    • 结果,我们得到了所有非负整数到具有k个非负整数的元组的映射
  • 最后,将第二步中给定的元组中的非负整数映射到第一步中具有相似公式的所有整数

我认为x<0?~x-x:x+x可以节省2个字节。
尼尔

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

与上述类似(使用N代替N0),但使用双射f的倒数:N ^ 2-> N via f(a, b) = 2^(a - 1)(2b - 1)


您的意思是……没有内置的Mathematica(当Alice拥有时)吗?我无语。
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+x可以节省2个字节。
尼尔

使用-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。无需回退,因为不再使用临时数组。(我尚未测试过,但它可能应该可以工作)
Herman L

另一字节可以通过更换被保存.fill('').fill(0),因为前导零都没有区别(至少不是当在Safari测试)
赫尔曼大号

@HermanLauenstein您尝试了.fill`` 吗?它可能会节省另外两个字节。
尼尔


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.