Funzione biiettiva ℤ → ℤⁿ


23

È banalmente possibile creare una funzione biiettiva da (l'insieme di tutti i numeri interi) a (ad esempio la funzione di identità).ZZZ

È anche possibile creare una funzione biiettiva da a (l'insieme di tutte le coppie di 2 numeri interi; il prodotto cartesiano di e ). Ad esempio, potremmo prendere il reticolo che rappresenta i punti interi su un piano 2D, disegnare una spirale da 0 verso l'esterno e quindi codificare coppie di numeri interi come distanza lungo la spirale quando interseca quel punto.Z 2ZZ2ZZZ

Spirale

(Una funzione che esegue questa operazione con i numeri naturali è nota come funzione di associazione .)

In effetti, esiste una famiglia di queste funzioni biiettive:

fk(x):ZZk

La sfida

Definire una famiglia di funzioni (dove è un numero intero positivo) con la proprietà che mappa biiettivamente numeri interi su -tuple di numeri interi.k f k ( x ) kfk(x)kfk(x)K

La tua richiesta dovrebbe, dati gli input e , restituire .x f k ( x )KXfk(X)

Questo è , quindi vince la risposta valida più breve (misurata in byte).

specificazioni

  • Qualsiasi famiglia può essere utilizzata purché soddisfi i criteri di cui sopra.fK(X)
  • Ti invitiamo ad aggiungere una descrizione di come funziona la tua famiglia di funzioni, nonché uno snippet per calcolare l'inverso della funzione (questo non è incluso nel conteggio dei byte).
  • Va bene se la funzione inversa è incomprensibile, purché sia ​​possibile dimostrarla, la funzione è biiettiva.
  • È possibile utilizzare qualsiasi rappresentazione adatta per numeri interi e elenchi di numeri interi firmati per la propria lingua, ma è necessario consentire che gli input alla funzione non siano associati.
  • Devi solo supportare valori di fino a 127.K

Va bene prendere una stringa versioni di ke xanziché numeri interi?
JungHwan Min

@JungHwanMin Le stringhe che rappresentano i numeri di input vanno bene.
Esolanging Fruit,

Risposte:


19

Alice , 14 12 byte

/O
\i@/t&Yd&

Provalo online!

Funzione inversa (non giocata a golf):

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

Provalo online!

Spiegazione

Alice ha una biiezione integrata tra e 2 , che può essere calcolata con Y(unpack) e il suo inverso Z (pack). Ecco un estratto dai documenti che spiegano la biiezione:

I dettagli della biiezione sono probabilmente irrilevanti per la maggior parte dei casi d'uso. Il punto principale è che consente all'utente di codificare due numeri interi in uno ed estrarre nuovamente i due numeri interi in seguito. Applicando ripetutamente il comando pack, interi elenchi o alberi di numeri interi possono essere memorizzati in un singolo numero (sebbene non in modo particolarmente efficiente in termini di memoria). La mappatura calcolata dall'operazione pack è una funzione biiettiva ℤ 2 → ℤ (ovvero una mappatura uno a uno). Innanzitutto, gli interi {..., -2, -1, 0, 1, 2, ...} sono mappati sui numeri naturali (incluso zero) come {..., 3, 1, 0, 2, 4 , ...}(in altre parole, i numeri interi negativi sono mappati ai naturali dispari e i numeri interi non negativi sono mappati ai naturali pari). I due numeri naturali vengono quindi mappati su uno tramite la funzione di accoppiamento Cantor , che scrive i naturali lungo le diagonali del primo quadrante della griglia intera. In particolare, {(0,0), (1,0), (0,1), (2,0), (1,1), (0,2), (3,0), ...} sono mappato su {0, 1, 2, 3, 4, 5, 6, ...} . Il numero naturale risultante viene quindi mappato di nuovo agli interi utilizzando l'inverso della biiezione precedente. Il comando unpack calcola esattamente l'inverso di questa mappatura.

Come accennato in precedenza, possiamo usare questa operazione di decompressione anche per mappare da a k . Dopo averlo applicato all'intero iniziale, possiamo decomprimere nuovamente il secondo intero del risultato, che ci fornisce un elenco di tre numeri interi. Quindi le applicazioni k-1Y ci danno come risultato interi k .

Possiamo calcolare l'inverso impacchettando l'elenco con Zdalla fine.

Quindi il programma stesso ha questa struttura:

/O
\i@/...d&

Questo è solo un modello base per un programma che legge un numero variabile di numeri decimali come input e stampa un numero variabile come risultato. Quindi il codice attuale è davvero solo:

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

Una cosa che vorrei affrontare è "perché Alice dovrebbe avere un built-in per una biiezione ℤ → ℤ 2 , non è quel territorio linguistico del golf"? Come con la maggior parte dei più strani incorporati di Alice, la ragione principale è il principio di progettazione di Alice secondo cui ogni comando ha due significati, uno per la modalità Cardinale (intero) e uno per la modalità Ordinale (stringa), e questi due significati dovrebbero essere in qualche modo correlati per dare Il modo cardinale e ordinale la sensazione di essere universi speculari in cui le cose sono in qualche modo uguali ma anche diverse. E abbastanza spesso ho avuto un comando per una delle due modalità che volevo aggiungere, e poi ho dovuto capire con quale altro comando associarlo.

Nel caso di Ye la Zmodalità Ordinale è venuta prima: volevo avere una funzione per intercalare due stringhe (zip) e separarle di nuovo (decomprimere). La qualità di ciò che volevo catturare in modalità Cardinale era quella di formare un numero intero da due ed essere in grado di estrarre nuovamente i due numeri interi in seguito, il che rende tale biiezione la scelta naturale.

Ho anche pensato che questo sarebbe effettivamente molto utile al di fuori del golf, perché ti consente di memorizzare un intero elenco o persino un albero di numeri interi in una singola unità di memoria (elemento stack, cella a nastro o cella a griglia).


Grande spiegazione come sempre
Luis Mendo,

Trovare Ye Znei documenti di Alice è in realtà ciò che mi ha spinto a pubblicare questa sfida (ci avevo pensato per un po ', ma questo mi ha ricordato).
Esolanging Fruit,

11

Python, 96 93 byte

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

Questo funziona in linea di principio convertendo il numero di input xin ternario bilanciato e quindi distribuendo i trits (cifre ternarie) prima meno significativi tra le diverse coordinate in modo round robin. Ad k=2esempio, ogni trit posizionata in modo uniforme contribuirebbe alla xcoordinata e ogni trit posizionata in modo dispari contribuirebbe alla ycoordinata. Perché k=3avresti il ​​primo, il quarto e il settimo tratto (ecc ...) a contribuire x, mentre il secondo, il quinto e l'ottavo contribuiscono ye il terzo, il sesto e il nono contribuiscono z.

Ad esempio, con k=2, guardiamo x=35. In ternario equilibrato, 35è 110T(usando la notazione dell'articolo di Wikipedia dove Trappresenta una -1cifra). La divisione dei tritoni dà 1T(la prima e la terza treccia, contando da destra) per la xcoordinata e 10(seconda e quarta treccia) per la ycoordinata. Convertendo ogni coordinata in decimale, otteniamo 2, 3.

Certo, in realtà non sto convertendo l'intero numero in ternario bilanciato contemporaneamente nel codice golf. Sto solo calcolando un trit alla volta (nella vvariabile) e aggiungendo il suo valore direttamente alle coordinate appropriate.

Ecco una funzione inversa non golfata che accetta un elenco di coordinate e restituisce un numero:

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

La mia ffunzione è forse notevole per le sue prestazioni. Utilizza solo O(k)memoria e richiede O(k) + O(log(x))tempo per trovare i risultati, quindi può funzionare con valori di input molto grandi. Provate f(10000, 10**10000)per esempio, e si otterrà una risposta più o meno istantaneamente (l'aggiunta di uno zero in più per l'esponente così xè 10**100000lo rende richiedere 30 secondi o giù di lì sul mio vecchio PC). La funzione inversa non è così veloce, soprattutto perché è difficile dire quando è terminata (scansiona tutte le coordinate dopo ogni modifica, quindi richiede qualcosa come il O(k*log(x))tempo). Probabilmente potrebbe essere ottimizzato per essere più veloce, ma probabilmente è già abbastanza veloce per i parametri normali.


Puoi rimuovere gli spazi (newline) all'interno del ciclo while
Mr. Xcoder,

Grazie, ho erroneamente pensato che ci fosse una sorta di conflitto tra un ciclo e l'utilizzo ;di istruzioni a catena su una sola riga.
Blckknght,

9

Buccia , 10 byte

§~!oΠR€Θݱ

Provalo online!

Anche la funzione inversa è di 10 byte.

§o!ȯ€ΠRΘݱ

Provalo online!

Spiegazione

Direzione in avanti:

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

Direzione inversa:

§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

Il prodotto cartesiano integrato Πsi comporta bene per infiniti elenchi, enumerando ogni k -tupla esattamente una volta.


[[0,1,-1,..],[[0,1,-1,..],[[0,1,-1,..]]questa parte dovrebbe essere [[0,1,-1,..],[0,1,-1,..],[0,1,-1,..]]?
Erik the Outgolfer,

@EriktheOutgolfer Umm sì, riparato ora.
Zgarb,

Questo è bellissimo. Come programmatore J, sai se esiste un buon modo per convertire una soluzione Lazy List come questa in J, che non le supporta? ^:^:_le soluzioni di tipo di solito finiscono per essere molto più ingombranti ...
Giona

@Jonah non ne sono sicuro. Potresti provare a calcolare l'array di tutte le k -tuple con le voci da i: xe ordinarlo per la somma dei valori assoluti, quindi indicizzarlo. L'idea è che questi array sono prefissi di un "array infinito" che contiene tutte le k -tuple.
Zgarb,

7

Wolfram Language (Mathematica) , 61 byte

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

Provalo online!

(Prende l'intero e quindi la lunghezza della tupla come input.)

Inverso:

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

Provalo online!

Come funziona

L'idea è semplice: trasformiamo l'input intero in un numero intero positivo (mappando 0,1,2,3, ... a 1,3,5,7, ... e -1, -2, -3, ... a 2,4,6, ...) e poi indice in tutte le k -tuples, filtrate per distanza dall'origine e poi di default tie-rottura di Mathematica.

Ma non possiamo utilizzare un elenco infinito, in modo che quando stiamo cercando il n ° k -tuple, si limita a generare k -tuples di numeri interi nella gamma {- n , ..., n }. Questo è garantito per essere sufficiente, perché il n ° più piccolo k -tuple dalla norma ha norma meno di n , e tutte le tuple della norma n o meno sono inclusi in questa lista.

Per l'inverso, generiamo solo un elenco abbastanza lungo di k -tuple, troviamo la posizione della k -tupla data in quell'elenco e quindi invertiamo l'operazione "piega in un numero intero positivo".


2
L'esecuzione con input ha [15, 5]
provocato l'arresto

2
Succederà. In linea di principio, l'algoritmo funziona per qualsiasi cosa, ma nel tuo caso funziona generando tutte e 5 le tuple dell'intervallo {-31, .., 31} e quindi prendendo il 31, quindi è piuttosto ad alta intensità di memoria.
Misha Lavrov,

3

J, 7 byte

#.,|:#:

Il codice J per farlo è imbarazzantemente semplice

Una funzione di accoppiamento molto semplice (o, funzione di tupling) è semplicemente interfogliare le cifre dell'espansione binaria di ciascuno dei numeri. Quindi, ad esempio, (47, 79)verrebbe abbinato come tale:

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

o, 6399. Ovviamente, possiamo banalmente generalizzare a qualsiasi n-tupla.

Esaminiamo come funziona verbo per verbo.

#:è anti-base due, se usato monadicamente restituisce l'espansione binaria di un numero. #: 47 79dà il risultato:

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

|:è l'operatore di trasposizione, che ruota semplicemente un array. Ruotando il risultato di si #: 47 79ottiene:

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

Se utilizzato monadicamente, ,è l'operatore ravel, produce un elenco monodimensionale da una tabella:

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

Infine, #.converte indietro l'espansione binaria, dandoci il risultato 6339.

Questa soluzione funzionerà per qualsiasi stringa di numeri interi.


7
Come funziona per i numeri negativi?
Neil,

2

Perl 6 , 148 byte

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

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

Spiegazione:

  • rect($n)è una funzione di aiuto che genera le coordinate dei punti integrali sul bordo di un rettangolo dalle coordinate (-$n,$n)a ($n, $n).

  • @spiral è un elenco pigro e infinito di punti integrali sui bordi di rettangoli di dimensioni crescenti, a partire da 0.

  • f($k)restituisce una funzione che è una biiezione dagli interi alle $k-tuple degli interi.

Se lo $kè 1, frestituisce il mapping delle identità -> $_ { $_ }.

Altrimenti, &gè la mappatura ottenuta ricorsivamente dagli interi alle $k-1-tuple degli interi.

Quindi, @spiralusciamo dall'origine e in ogni punto formiamo una $ktupla prendendo la coordinata X e il risultato appiattito della chiamata gcon la coordinata Y. Questa mappatura generata pigramente è memorizzata nell'array @v.

@vcontiene tutte le $ktuple che iniziano con l'indice 0, quindi per estendere l'indicizzazione agli interi negativi, mappiamo solo input positivi ai numeri pari e input negativi ai numeri dispari. Viene restituita una funzione (chiusura) che cerca gli elementi @vin questo modo.


2

JavaScript, 155 byte

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)

Versione di 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;
  });
}
  • Innanzitutto, mappiamo tutti gli interi su tutti gli interi non negativi uno per uno:
    • se n> 0 quindi risultato = n * 2
    • altrimenti risulta = -n * 2 - 1
  • In secondo luogo, diamo un ordine a tutte le tuple con numeri interi non negativi di lunghezza k:
    • calcola la somma di tutti gli elementi, uno più piccolo viene per primo
    • se la somma è uguale, confronta da sinistra a destra, prima ne arriva una più piccola
    • Di conseguenza, abbiamo ottenuto la mappa per tutti gli interi non negativi in ​​tuple con k interi non negativi
  • Infine, mappa gli interi non negativi in ​​tupla dati nel secondo passaggio a tutti gli interi con formula simile nel primo passaggio

Penso che x<0?~x-x:x+xsalva 2 byte.
Neil,

2

Wolfram Language (Mathematica) , 107 byte

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

Provalo online!

Inverso, 60 byte

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

Provalo online!

Spiegazione:

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

N0 -> N0 ^ 2 tramite l' inverso della funzione di associazione

N0 -> N0 ^ k Applica ripetutamente quanto sopra al numero più a sinistra fino a ottenere la lunghezza k

N0 ^ k -> Z ^ k via f(n) = (-1)^n * ceil(n/2), per quanto riguarda l'elemento


Mathematica, 101 byte

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

Simile a sopra (usa N invece di N0), ma usa l'inverso della biiezione f: N ^ 2 -> N via f(a, b) = 2^(a - 1)(2b - 1)


Intendi ... non esiste un Mathematica integrato (quando Alice ne ha uno)? Io sono senza parole.
JayCe,

1

JavaScript, 112 byte

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. convertire in non negativo
  2. (n * k + i) th digit to i-th number
  3. riconvertire

@HermanLauenstein non ha bisogno di tornare indietro?
TSH

Penso che x<0?~x-x:x+xsalva 2 byte.
Neil,

-5 byte utilizzando [...BT${x<0?~x-x:x+x}BT].reverse().map((c,i)=>r[i%k]+=c),(credito a @Neil per x<0?~x-x:x+x). .reverse()viene utilizzato anziché (s.length-i)poiché evita la necessità del parametro aggiuntivo sper il primo .map. Non è necessario tornare indietro poiché l'array temporaneo non viene più utilizzato. (Non l'ho provato ma probabilmente dovrebbe funzionare)
Herman L

Un altro byte può essere salvato sostituendolo .fill('')con .fill(0), poiché uno zero iniziale non fa alcuna differenza (almeno non quando viene testato in Safari)
Herman L

@HermanLauenstein Hai provato .fill`` ? Potrebbe salvare un altro paio di byte.
Neil,


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.