Função bijetiva ℤ → ℤⁿ


23

É trivialmente possível criar uma função bijetiva de (o conjunto de todos os números inteiros) a Z (por exemplo, a função de identidade).ZZ

Também é possível criar uma função bijetiva de a Z 2 (o conjunto de todos os pares de 2 números inteiros; o produto cartesiano de Z e Z ). Por exemplo, podemos pegar a rede que representa pontos inteiros em um plano 2D, desenhar uma espiral de 0 para fora e, em seguida, codificar pares de números inteiros como a distância ao longo da espiral quando cruzar esse ponto.ZZ2ZZ

Espiral

(Uma função que faz isso com números naturais é conhecida como função de emparelhamento .)

De fato, existe uma família dessas funções bijetivas:

fk(x):ZZk

O desafio

Defina uma família de funções (onde k é um número inteiro positivo) com a propriedade que f k ( x ) mapeia bijetivamente números inteiros para k - pares de inteiros.fk(x)kfk(x)k

Seu envio deve, com as entradas e x , retornar f k ( x ) .kxfk(x)

Isso é , então a resposta mais curta e válida (medida em bytes) vence.

Especificações

  • Qualquer família pode ser usada desde que cumpra os critérios acima.fk(x)
  • Você é incentivado a adicionar uma descrição de como sua família de funções funciona, bem como um trecho para calcular o inverso da função (isso não está incluído na sua contagem de bytes).
  • Não há problema se a função inversa for incontestável, desde que você possa provar que a função é bijetiva.
  • Você pode usar qualquer representação adequada para números inteiros assinados e listas de números inteiros assinados para o seu idioma, mas deve permitir que entradas para sua função sejam ilimitadas.
  • Você só precisa suportar valores de até 127.k

Não há problema em obter versões de uma sequência de caracteres ke em xvez de números inteiros?
JungHwan Min 3/11

@JungHwanMin As strings que representam os números de entrada estão corretas.
Esolanging Fruit

Respostas:


19

Alice , 14 12 bytes

/O
\i@/t&Yd&

Experimente online!

Função inversa (sem jogar golfe):

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

Experimente online!

Explicação

Alice possui uma bijeção interna entre e 2 , que pode ser calculada com Y(descompactar) e seu inverso Z (pacote). Aqui está um trecho dos documentos que explicam a bijeção:

Os detalhes da bijeção são provavelmente irrelevantes para a maioria dos casos de uso. O ponto principal é que ele permite que o usuário codifique dois números inteiros em um e extraia os dois inteiros novamente mais tarde. Aplicando o comando pack repetidamente, listas inteiras ou árvores de números inteiros podem ser armazenadas em um único número (embora não de maneira particularmente eficiente em termos de memória). O mapeamento calculado pela operação do pacote é uma função bijetiva 2 → ℤ (ou seja, um mapeamento individual). Primeiro, os números inteiros {..., -2, -1, 0, 1, 2, ...} são mapeados para os números naturais (incluindo zero), como {..., 3, 1, 0, 2, 4 , ...}(em outras palavras, números inteiros negativos são mapeados para naturais ímpares e números inteiros não negativos são mapeados para pares naturais). Os dois números naturais são mapeados para um através da função de emparelhamento Cantor , que grava os naturais ao longo das diagonais do primeiro quadrante da grade inteira. Especificamente, {(0,0), (1,0), (0,1), (2,0), (1,1), (0,2), (3,0), ...} são mapeado para {0, 1, 2, 3, 4, 5, 6, ...} . O número natural resultante é então mapeado de volta para os números inteiros usando o inverso da bijeção anterior. O comando descompactar calcula exatamente o inverso desse mapeamento.

Como mencionado acima, podemos utilizar esta operação descompactar para mapear para k bem. Depois de aplicá-lo ao número inteiro inicial, podemos descompactar o segundo número inteiro do resultado novamente, o que nos fornece uma lista de três números inteiros. Portanto , as aplicações k-1Y nos fornecem k números inteiros como resultado.

Podemos calcular o inverso colocando a lista Zno final.

Portanto, o próprio programa tem essa estrutura:

/O
\i@/...d&

Este é apenas um modelo básico para um programa que lê um número variável de números inteiros decimais como entrada e imprime um número variável como resultado. Portanto, o código real é realmente apenas:

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

Uma coisa que eu gostaria de abordar é "por que Alice teria um built-in para uma bijeção ℤ → ℤ 2 , não é esse território da linguagem do golfe"? Como na maioria dos built-ins mais estranhos de Alice, o principal motivo é o princípio de design de Alice, de que todo comando tem dois significados, um para o modo Cardinal (inteiro) e outro para o modo Ordinal (string), e esses dois significados devem estar de alguma forma relacionados a dar Cardeal e Ordinal modificam a sensação de que são universos espelhos, onde as coisas são iguais, mas também diferentes. E muitas vezes eu tinha um comando para um dos dois modos que queria adicionar e, em seguida, tive que descobrir com qual outro comando o emparelharia.

No caso de Ye Zmodo ordinal veio primeiro: Eu queria ter uma função para intercalar duas cadeias (zip) e separá-los novamente (unzip). A qualidade disso que eu queria capturar no modo Cardinal era formar um número inteiro de dois e ser capaz de extrair os dois inteiros mais tarde, o que torna essa bijeção a escolha natural.

Também imaginei que isso seria realmente muito útil fora do golfe, porque permite armazenar uma lista inteira ou até uma árvore de números inteiros em uma única unidade de memória (elemento de pilha, célula de fita ou célula de grade).


Grande explicação como sempre
Luis Mendo

Encontrar Ye Znos documentos da Alice foi o que me levou a postar esse desafio (eu já pensava nisso há algum tempo, mas isso me lembrou).
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

Isso funciona, em princípio, convertendo o número de entrada xem ternário balanceado e depois distribuindo os trits (dígitos ternários) menos significativos primeiro entre as diferentes coordenadas de maneira round robin. Assim, por k=2exemplo, todo trit posicionado par contribuiria para a xcoordenada, e todo trit posicionado ímpar contribuiria para a ycoordenada. Pois k=3você teria o primeiro, quarto e sétimo trits (etc ...) contribuindo para x, enquanto o segundo, quinto e oitavo contribuem para y, e o terceiro, sexto e nono contribuem para z.

Por exemplo, com k=2, vamos ver x=35. No ternário balanceado, 35é 110T(usando a notação do artigo da Wikipedia onde Trepresenta um -1dígito). Dividir os trits acima fornece 1T(o primeiro e o terceiro trits, contando da direita) para a xcoordenada e 10(segundo e quarto trits) para a ycoordenada. Convertendo cada coordenada de volta para decimal, obtemos 2, 3.

Obviamente, não estou convertendo o número inteiro para um ternário equilibrado de uma só vez no código do golfe. Estou apenas computando um trit de cada vez (na vvariável) e adicionando seu valor diretamente à coordenada apropriada.

Aqui está uma função inversa não-gasta que pega uma lista de coordenadas e retorna um 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

Minha ffunção talvez seja notável por seu desempenho. Ele usa apenas O(k)memória e leva O(k) + O(log(x))tempo para encontrar os resultados, para que possa trabalhar com valores de entrada muito grandes. Tente f(10000, 10**10000), por exemplo, e você terá uma resposta praticamente instantaneamente (adicionando um zero a mais para o expoente assim xé 10**100000torna demorar 30 segundos ou mais no meu velho PC). A função inversa não é tão rápida, principalmente porque é difícil saber quando está pronta (verifica todas as coordenadas após cada alteração, por isso leva algo como O(k*log(x))tempo). Provavelmente poderia ser otimizado para ser mais rápido, mas provavelmente já é rápido o suficiente para parâmetros normais.


Você pode remover os espaços (newlines) dentro do loop while
Mr. Xcoder

Obrigado, pensei erroneamente que havia algum tipo de conflito entre um loop e o uso ;de encadear instruções em uma única linha.
precisa saber é o seguinte

9

Casca , 10 bytes

§~!oΠR€Θݱ

Experimente online!

A função inversa também tem 10 bytes.

§o!ȯ€ΠRΘݱ

Experimente online!

Explicação

Sentido dianteiro:

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

Direção oposta:

§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

O produto cartesiano interno Πcomporta - se muito bem para listas infinitas, enumerando cada k -uplo exatamente uma vez.


[[0,1,-1,..],[[0,1,-1,..],[[0,1,-1,..]]essa parte deveria ser [[0,1,-1,..],[0,1,-1,..],[0,1,-1,..]]?
Erik the Outgolfer

@EriktheOutgolfer Umm sim, corrigido agora.
Zgarb

Isso é lindo. Como programador J, você sabe se existe uma boa maneira de converter uma solução de lista lenta como essa em J, que não as suporta? ^:^:_soluções do tipo geralmente acabam muito mais complicado ...
Jonah

@ Jonah não tenho certeza. Você pode tentar calcular a matriz de todos os k- pares com entradas de i: xe classificá-la pela soma dos valores absolutos e depois indexá-la. A idéia é que essas matrizes sejam prefixos de uma "matriz infinita" que contém todos os k- pares.
Zgarb

7

Wolfram Language (Mathematica) , 61 bytes

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

Experimente online!

(Pega o número inteiro e, em seguida, o comprimento da tupla como entrada.)

Inverso:

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

Experimente online!

Como funciona

A idéia é direta: transformamos a entrada inteira em um número inteiro positivo (mapeando 0,1,2,3, ... para 1,3,5,7, ... e -1, -2, -3, ... a 2,4,6, ...) e depois indexe em todos os k- pares, classificados por distância da origem e, em seguida, pelo desempate padrão do Mathematica.

Mas não podemos usar uma lista infinita, então quando nós estamos olhando para o n º k -tuple, temos apenas gerar k -tuples de inteiros no intervalo {- n , ..., n }. Isto é garantido para ser o suficiente, porque o n º menor k -tuple por norma tem norma inferior a n , e todas as tuplas da norma n ou menos estão incluídos nesta lista.

Para o inverso, apenas geramos uma lista suficientemente longa de k- pares, localizamos a posição do dado k- duplo nessa lista e invertemos a operação "fold em um número inteiro positivo".


2
Correr com entradas [15, 5]
causou um erro no

2
Isso vai acontecer. Em princípio, o algoritmo funciona para qualquer coisa, mas, no seu caso, gera todas as cinco tuplas do intervalo {-31, .., 31} e, depois, a 31ª, por isso é bastante intensiva em memória.
Misha Lavrov #

3

J, 7 bytes

#.,|:#:

O código J para fazer isso de forma embaraçosamente simples

Uma função de emparelhamento muito simples (ou função de tupling) é simplesmente intercalar os dígitos da expansão binária de cada um dos números. Assim, por exemplo, (47, 79)seria emparelhado como tal:

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

ou 6399. Obviamente, podemos generalizar trivialmente para qualquer n-tupla.

Vamos examinar como isso funciona verbo por verbo.

#:é anti-base dois, quando usado monadicamente retorna a expansão binária de um número. #: 47 79dá o resultado:

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

|:é o operador de transposição, que simplesmente gira uma matriz. Girar o resultado de #: 47 79dá:

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

Quando usado monadicamente, ,é o operador ravel, ele produz uma lista unidimensional a partir de uma tabela:

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

Finalmente, #.converte a expansão binária de volta, fornecendo o resultado 6339.

Esta solução funcionará para qualquer sequência de números inteiros.


7
Como isso funciona 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]}}}

Experimente online!

Ungolfed:

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

Explicação:

  • rect($n)é uma função auxiliar que gera as coordenadas dos pontos integrais na aresta de um retângulo de coordenadas (-$n,$n)a ($n, $n).

  • @spiral é uma lista preguiçosa e infinita dos pontos integrais nas bordas dos retângulos de tamanho crescente, começando em 0.

  • f($k)retorna uma função que é uma bijeção dos números inteiros para $k-tuplos de números inteiros.

Se $kfor 1, fretorna o mapeamento de identidade-> $_ { $_ } .

Caso contrário, &gé o mapeamento recursivamente obtido dos números inteiros para$k-1 -tuplos de números inteiros.

Então, saímos @spiralda origem e, em cada ponto, formamos um $kmúltiplo, obtendo a coordenada X e o resultado achatado da chamada gcom a coordenada Y. Esse mapeamento gerado lentamente é armazenado na matriz@v .

@vcontém todos os $k-tuplos começando no índice 0, portanto, para estender a indexação para números inteiros negativos, apenas mapeamos entradas positivas para os números pares e entradas negativas para os números ímpares. É retornada uma função (fechamento) que procura elementos @vdessa maneira.


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)

Versão de pré-verificação:

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;
  });
}
  • Primeiro, mapeamos todos os números inteiros para todos os números inteiros não negativos, um por um:
    • se n> 0 então result = n * 2
    • caso contrário, resultado = -n * 2-1
  • Segundo, damos a todas as tuplas com números inteiros não negativos de comprimento k uma ordem:
    • calcular a soma de todos os elementos, um menor vem primeiro
    • se a soma for igual, compare da esquerda para a direita, menor será a primeira
    • Como resultado, obtivemos o mapa de todos os números inteiros não negativos para tuplas com k números inteiros não negativos
  • Por fim, mapeie números inteiros não negativos na tupla, fornecidos na segunda etapa, para todos os números inteiros com fórmula semelhante na primeira etapa

Eu acho que x<0?~x-x:x+xeconomiza 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]&

Experimente online!

Inverso, 60 bytes

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

Experimente online!

Explicação:

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

N0 -> N0 ^ 2 via inversa da função de emparelhamento

N0 -> N0 ^ k Aplique repetidamente o acima no número mais à esquerda até obtermos comprimento k

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


Mathematica, 101 bytes

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

Semelhante ao anterior (usa N em vez de N0), mas usa o inverso da bijeção f: N ^ 2 -> N via f(a, b) = 2^(a - 1)(2b - 1)


Você quer dizer ... não há Mathematica embutido para isso (quando Alice tem um)? Estou sem palavras.
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. converter para não negativo
  2. (n * k + i) do dígito ao i-ésimo número
  3. converter de volta

@HermanLauenstein não precisa voltar atrás?
tsh

Eu acho que x<0?~x-x:x+xeconomiza 2 bytes.
Neil

-5 bytes usando [...BT${x<0?~x-x:x+x}BT].reverse().map((c,i)=>r[i%k]+=c),(crédito para @Neil x<0?~x-x:x+x). .reverse()é usado em vez de, (s.length-i)pois evita a necessidade do parâmetro extra spara o primeiro .map. Não há necessidade de reverter, pois a matriz temporária não é usada novamente. (Eu não testei, mas provavelmente deve funcionar)
Herman L

Outro byte pode ser salvo substituindo .fill('')por .fill(0), já que o zero inicial não faz diferença (pelo menos não quando testado no Safari)
Herman L

@HermanLauenstein Você tentou .fill`` ? Pode salvar mais alguns bytes.
Neil


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.