Función biyectiva ℤ → ℤⁿ


23

Es trivialmente posible crear una función biyectiva de (el conjunto de todos los enteros) a Z (por ejemplo, la función de identidad).ZZ

También es posible crear una función biyectiva de a Z 2 (el conjunto de todos los pares de 2 enteros; el producto cartesiano de Z y Z ). Por ejemplo, podríamos tomar la red que representa puntos enteros en un plano 2D, dibujar una espiral desde 0 hacia afuera y luego codificar pares de enteros como la distancia a lo largo de la espiral cuando se cruza con ese punto.ZZ2ZZ

Espiral

(Una función que hace esto con números naturales se conoce como función de emparejamiento ).

De hecho, existe una familia de estas funciones biyectivas:

fk(x):ZZk

El reto

Defina una familia de funciones (donde k es un entero positivo) con la propiedad de que f k ( x ) mapea bijetivamente enteros a k -tuplas de enteros.fk(x)kfk(x)k

Su envío debe, dadas las entradas y x , devolver f k ( x ) .kxfk(x)

Este es el , por lo que gana la respuesta válida más corta (medida en bytes).

Presupuesto

  • Se puede usar cualquier familia siempre que cumpla con los criterios anteriores.fk(x)
  • Se le recomienda agregar una descripción de cómo funciona su familia de funciones, así como un fragmento para calcular el inverso de la función (esto no está incluido en su recuento de bytes).
  • Está bien si la función inversa no es computable, siempre que pueda demostrar que la función es biyectiva.
  • Puede usar cualquier representación adecuada para enteros con signo y listas de enteros con signo para su idioma, pero debe permitir que las entradas a su función sean ilimitadas.
  • Solo necesita admitir valores de hasta 127.k

¿Está bien tomar una cadena de versiones de ky en xlugar de enteros?
JungHwan Min

@JungHwanMin Las cadenas que representan los números de entrada están bien.
Esolanging Fruit

Respuestas:


19

Alicia , 14 12 bytes

/O
\i@/t&Yd&

Pruébalo en línea!

Función inversa (no golfizada):

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

Pruébalo en línea!

Explicación

Alice tiene una biyección integrada entre y 2 , que se puede calcular con Y(desempaquetar) y su inverso Z (paquete). Aquí hay un extracto de los documentos que explican la biyección:

Los detalles de la biyección probablemente sean irrelevantes para la mayoría de los casos de uso. El punto principal es que permite al usuario codificar dos enteros en uno y extraer los dos enteros nuevamente más adelante. Al aplicar el comando pack repetidamente, se pueden almacenar listas enteras o árboles de enteros en un solo número (aunque no de una manera particularmente eficiente en la memoria). La asignación calculada por la operación del paquete es una función biyectiva 2 → ℤ (es decir, una asignación uno a uno). Primero, los enteros {..., -2, -1, 0, 1, 2, ...} se asignan a los números naturales (incluido el cero) como {..., 3, 1, 0, 2, 4 , ...}(en otras palabras, los enteros negativos se asignan a naturales impares y los enteros no negativos se asignan a naturales pares). Los dos números naturales se asignan a uno a través de la función de emparejamiento de Cantor , que escribe los naturales a lo largo de las diagonales del primer cuadrante de la cuadrícula entera. Específicamente, {(0,0), (1,0), (0,1), (2,0), (1,1), (0,2), (3,0), ...} son mapeado a {0, 1, 2, 3, 4, 5, 6, ...} . El número natural resultante se vuelve a asignar a los enteros utilizando el inverso de la biyección anterior. El comando desempaquetar calcula exactamente el inverso de esta asignación.

Como se mencionó anteriormente, también podemos usar esta operación de desempaquetado para asignar a k . Después de aplicarlo al entero inicial, podemos desempaquetar el segundo entero del resultado nuevamente, lo que nos da una lista de tres enteros. Entonces , las aplicaciones k-1 de Ynos dan k enteros como resultado.

Podemos calcular el inverso empacando la lista Zdesde el final.

Entonces el programa en sí tiene esta estructura:

/O
\i@/...d&

Esta es solo una plantilla básica para un programa que lee un número variable de enteros decimales como entrada e imprime un número variable como resultado. Entonces, el código real es realmente justo:

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

Una cosa que me gustaría abordar es "¿por qué Alice tiene una función incorporada para una biyección ℤ → ℤ 2 , no es ese territorio de lenguaje de golf"? Al igual que con la mayoría de las incorporaciones más extrañas de Alice, la razón principal es el principio de diseño de Alice de que cada comando tiene dos significados, uno para el modo Cardinal (entero) y otro para el modo Ordinal (string), y estos dos significados deberían estar relacionados de alguna manera para dar Cardinal y Ordinal moderan la sensación de que son universos espejo donde las cosas son más o menos iguales pero también diferentes. Y a menudo tenía un comando para uno de los dos modos que quería agregar, y luego tenía que averiguar con qué otro comando emparejarlo.

En el caso de Yy el Zmodo Ordinal fue lo primero: quería tener una función para intercalar dos cadenas (zip) y separarlas nuevamente (descomprimir). La calidad de esto que quería capturar en el modo Cardinal era formar un número entero a partir de dos y poder extraer los dos enteros nuevamente más tarde, lo que hace que dicha biyección sea la elección natural.

También pensé que esto sería realmente muy útil fuera del golf, porque te permite almacenar una lista completa o incluso un árbol de enteros en una sola unidad de memoria (elemento de pila, celda de cinta o celda de cuadrícula).


Gran explicación como siempre
Luis Mendo

Encontrar Yy Zen los documentos de Alice es en realidad lo que me impulsó a publicar este desafío (lo había estado pensando por un tiempo, pero esto me recordó).
Esolanging Fruit

11

Python, 96 93 bytes

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

En principio, esto funciona al convertir el número de entrada xen ternario equilibrado y luego distribuir los trits (dígitos ternarios) menos significativos primero entre las diferentes coordenadas de forma circular. Entonces, por k=2ejemplo, cada trit posicionado en forma pareja contribuiría a la xcoordenada, y cada trit posicionado en forma impar contribuiría a la ycoordenada. Porque k=3tendrías el primero, el cuarto y el séptimo trits (etc.) contribuyendo x, mientras que el segundo, quinto y octavo contribuyen y, y el tercero, sexto y noveno contribuyen z.

Por ejemplo, con k=2, echemos un vistazo x=35. En ternario equilibrado, 35es 110T(usando la notación del artículo de Wikipedia donde Trepresenta un -1dígito). Al dividir los trits se obtienen 1T(el primer y el tercer trits, contando desde la derecha) para la xcoordenada y 10(segundo y cuarto trits) para la ycoordenada. Al convertir cada coordenada de nuevo a decimal, obtenemos 2, 3.

Por supuesto, en realidad no estoy convirtiendo el número entero a ternario equilibrado a la vez en el código de golf. Solo estoy calculando un trit a la vez (en la vvariable) y agrego su valor directamente a la coordenada adecuada.

Aquí hay una función inversa sin golf que toma una lista de coordenadas y devuelve un número:

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

Mi ffunción es quizás notable por su rendimiento. Utiliza solo O(k)memoria y lleva O(k) + O(log(x))tiempo encontrar los resultados, por lo que puede funcionar con valores de entrada muy grandes. Intenta f(10000, 10**10000)por ejemplo, y obtendrá una respuesta más o menos al instante (añadiendo un cero de más al exponente modo xes 10**100000marcas se tarda 30 segundos más o menos de mi viejo PC). La función inversa no es tan rápida, principalmente porque es difícil saber cuándo se hace (escanea todas las coordenadas después de cada cambio, por lo que toma algo de O(k*log(x))tiempo). Probablemente podría optimizarse para ser más rápido, pero probablemente ya sea lo suficientemente rápido para los parámetros normales.


Puede eliminar los espacios (líneas nuevas) dentro del bucle while
Sr. Xcoder

Gracias, erróneamente pensé que había algún tipo de conflicto entre un bucle y el uso ;de cadenas de declaraciones en una sola línea.
Blckknght

9

Casco , 10 bytes

§~!oΠR€Θݱ

Pruébalo en línea!

La función inversa también es de 10 bytes.

§o!ȯ€ΠRΘݱ

Pruébalo en línea!

Explicación

Dirección hacia adelante:

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

Direccion contraria:

§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

El producto integrado cartesiano se Πcomporta bien para listas infinitas, enumerando cada k -tupla exactamente una vez.


[[0,1,-1,..],[[0,1,-1,..],[[0,1,-1,..]]se supone que esta parte es [[0,1,-1,..],[0,1,-1,..],[0,1,-1,..]]?
Erik the Outgolfer

@EriktheOutgolfer Umm sí, arreglado ahora.
Zgarb

Esto es hermoso. Como programador de J, ¿sabe si hay una buena manera de convertir una solución de lista perezosa como esta en J, que no los admite? ^:^:_las soluciones de tipo generalmente terminan mucho más engorrosas ...
Jonás

@ Jonás, no estoy seguro. Podrías tratar de calcular la matriz de todas las k -tuplas con entradas de i: xy ordenarlas por la suma de valores absolutos, luego indexarlas. La idea es que estas matrices son prefijos de una "matriz infinita" que contiene todas las k -tuplas.
Zgarb

7

Wolfram Language (Mathematica) , 61 bytes

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

Pruébalo en línea!

(Toma el entero y luego la longitud de la tupla como entrada).

Inverso:

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

Pruébalo en línea!

Cómo funciona

La idea es sencilla: convertimos la entrada de entero en un entero positivo (asignando 0,1,2,3, ... a 1,3,5,7, ... y -1, -2, -3, ... a 2,4,6, ...) y luego indexar en todas las k -tuplas, ordenadas por distancia desde el origen y luego por el desempate predeterminado de Mathematica.

Pero no podemos usar una lista infinita, así que cuando buscamos la n- ésima k -tupla, solo generamos k -tuplas de enteros en el rango {- n , ..., n }. Esto se garantiza que sea suficiente, ya que el n º más pequeño k tupla por norma tiene norma menor que n , y todas las tuplas de la norma n o menos están incluidos en esta lista.

Para la inversa, sólo generamos una lista suficientemente largo de k -tuplas, encontramos la posición de lo dado k tupla en esa lista, y luego invertir la operación de "plegarse en un número entero positivo".


2
Ejecutar con entradas [15, 5]bloqueó mi PC ...
JungHwan Min

2
Eso sucederá En principio, el algoritmo funciona para cualquier cosa, pero en su caso funciona generando las 5 tuplas desde el rango {-31, ..., 31} y luego tomando la 31ª, por lo que requiere bastante memoria.
Misha Lavrov

3

J, 7 bytes

#.,|:#:

El código J para hacer esto es vergonzosamente simple

Una función de emparejamiento muy simple (o función de tupla) es simplemente intercalar los dígitos de la expansión binaria de cada uno de los números. Entonces, por ejemplo (47, 79), se emparejaría como tal:

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

o, 6399. Obviamente, podemos generalizar trivialmente a cualquier n-tupla.

Examinemos cómo funciona esto verbo por verbo.

#:es anti-base dos, cuando se usa monádicamente devuelve la expansión binaria de un número. #: 47 79da el resultado:

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

|:es el operador de transposición, que simplemente gira una matriz. Rotando el resultado de #: 47 79da:

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

Cuando se usa monádicamente, ,es el operador de desvello, produce una lista unidimensional a partir de una tabla:

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

Finalmente, #.convierte la expansión binaria de nuevo, dándonos el resultado 6339.

Esta solución funcionará para cualquier cadena de enteros.


77
¿Cómo funciona esto para números negativos?
Neil

2

Perl 6 , 148 bytes

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

Pruébalo en línea!

Sin golf:

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

Explicación:

  • rect($n)es una función auxiliar que genera las coordenadas de los puntos integrales en el borde de un rectángulo desde las coordenadas (-$n,$n)hasta ($n, $n).

  • @spiral es una lista perezosa e infinita de los puntos integrales en los bordes de los rectángulos de tamaño creciente, comenzando desde 0.

  • f($k)devuelve una función que es una biyección de los enteros a $k-tuplas de enteros.

Si $kes así 1, fdevuelve el mapeo de identidad -> $_ { $_ }.

De lo contrario, &ges la asignación obtenida recursivamente de los enteros a $k-1-tuplas de enteros.

Luego, salimos @spiraldel origen, y en cada punto formamos una $ktupla tomando la coordenada X y el resultado aplanado de llamar gcon la coordenada Y. Esta asignación generosamente perezosa se almacena en la matriz @v.

@vcontiene todas las $ktuplas que comienzan con el índice 0, por lo que para extender la indexación a los enteros negativos, simplemente asignamos entradas positivas a los números pares y entradas negativas a los números impares. Se devuelve una función (cierre) que busca elementos de @vesta manera.


2

JavaScript, 155 bytes

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)

Versión de 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;
  });
}
  • Primero, asignamos todos los enteros a todos los enteros no negativos uno por uno:
    • si n> 0 entonces resultado = n * 2
    • de lo contrario resultado = -n * 2 - 1
  • En segundo lugar, damos un orden a todas las tuplas con enteros no negativos de longitud k:
    • calcular la suma de todos los elementos, el más pequeño es lo primero
    • si la suma es igual, compare de izquierda a derecha, la más pequeña es lo primero
    • Como resultado, obtuvimos el mapa para todos los enteros no negativos a tuplas con k enteros no negativos
  • Finalmente, asigne enteros no negativos en la tupla dada en el segundo paso a todos los enteros con una fórmula similar en el primer paso

Creo que x<0?~x-x:x+xahorra 2 bytes.
Neil

2

Wolfram Language (Mathematica) , 107 bytes

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

Pruébalo en línea!

Inverso, 60 bytes

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

Pruébalo en línea!

Explicación:

Z -> N0 vía f(n) = 2n if n>=0 and -2n-1 if n<0

N0 -> N0 ^ 2 vía inversa de la función de emparejamiento

N0 -> N0 ^ k Aplica repetidamente lo anterior al número más a la izquierda hasta obtener la longitud k

N0 ^ k -> Z ^ k vía f(n) = (-1)^n * ceil(n/2), elemento sabio


Mathematica, 101 bytes

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

Similar al anterior (usa N en lugar de N0), pero usa el inverso de la biyección f: N ^ 2 -> N vía f(a, b) = 2^(a - 1)(2b - 1)


¿Quieres decir ... no hay Mathematica incorporado para eso (cuando Alice tiene uno)? Estoy sin palabras
JayCe

1

JavaScript, 112 bytes

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. convertir a no negativo
  2. (n * k + i) th dígito a i-ésimo número
  3. convertir de nuevo

@HermanLauenstein no necesita retroceder?
tsh

Creo que x<0?~x-x:x+xahorra 2 bytes.
Neil

-5 bytes usando [...BT${x<0?~x-x:x+x}BT].reverse().map((c,i)=>r[i%k]+=c),(crédito a @Neil para x<0?~x-x:x+x). .reverse()se usa en lugar de (s.length-i)ya que evita la necesidad del parámetro adicional sal primero .map. No hay necesidad de retroceder ya que la matriz temporal no se usa nuevamente. (No lo he probado pero probablemente debería funcionar)
Herman L

Se puede guardar otro byte reemplazándolo .fill('')con .fill(0), ya que un cero inicial no hace ninguna diferencia (al menos no cuando se prueba en Safari)
Herman L

@HermanLauenstein ¿Lo intentaste .fill`` ? Podría ahorrar otro par de bytes.
Neil


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.