È possibile comprimere i dati casuali di


19

Ho dei dati reali che sto usando per un gioco di carte simulato. Sono interessato solo ai ranghi delle carte, non ai semi. Tuttavia è un mazzo standard da carte, quindi nel mazzo ci sono solo di ogni valore. Il mazzo viene mischiato bene per ogni mano, e quindi metto l'intero mazzo in un file. Quindi ci sono solo possibili simboli nel file di output che sono . ( = dieci gradi). Quindi ovviamente possiamo comprimere questi bit usando bit per simbolo, ma poi stiamo sprecando delle possibili codifiche. Possiamo fare meglio se raggruppiamo simboli alla volta e li comprimiamo, perché4 13 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , T , J , Q , K , A T 4 3 16 4 13 4524132,3,4,5,6,7,8,9,T,J,Q,K,AT43164134 = e che può adattarsi piuttosto "comodamente" in bit anziché . Il limite teorico di bitpacking è log ( ) / log ( ) = per i dati con simboli casuali per ogni possibile scheda. Tuttavia non possiamo avere re ad esempio in questo mazzo. DOVREBBE avere solo di ogni rango in ciascun mazzo, quindi la codifica dell'entropia scende di circa mezzo bit per simbolo a circa .15 16 13 2 3.70044 13 52 4 3.228,56115161323.70044135243.2

Ok, quindi ecco cosa sto pensando. Questi dati non sono totalmente casuali. Sappiamo che ce ne sono per ogni valore, quindi in ogni blocco di carte (chiamalo mazzo mischiato), quindi possiamo fare diverse ipotesi e ottimizzazioni. Una di queste non dobbiamo codificare l'ultima carta, perché sapremo quale dovrebbe essere. Un altro risparmio sarebbe se finissimo con un solo grado; ad esempio, se le ultime carte nel mazzo sono , non dovremmo codificarle perché il decodificatore conterebbe le carte fino a quel punto e vedrà che tutti gli altri ranghi sono stati riempiti e assumerà il " le carte mancanti "sono tutte s.52 3 777 3 7452377737

Quindi la mia domanda a questo sito è: quali altre ottimizzazioni sono possibili per ottenere un file di output ancora più piccolo su questo tipo di dati, e se li usiamo, possiamo mai battere l'entropia teorica (semplice) di bitpacking di bit per simbolo, o persino avvicinarsi al limite massimo di entropia di circa bit per simbolo in media? Se é cosi, come?3.23.700443.2

Quando uso un programma di tipo ZIP (ad esempio WinZip), vedo solo una compressione , che mi dice che sta facendo un bitpack "pigro" a bit. Se "precomprimo" i dati usando il mio bitpacking, sembra che mi piaccia di più, perché quando lo eseguo attraverso un programma zip, ottengo una compressione leggermente superiore a . Quello che sto pensando è, perché non fare tutta la compressione da solo (perché ho più conoscenza dei dati rispetto al programma Zip). Mi chiedo se posso battere l'entropia "limite" di log ( ) / log ( ) =2:12 : 1 13 2 3.7004442:11323.70044. Sospetto di poterlo fare con i pochi "trucchi" che ho citato e probabilmente ne scoprirò alcuni altri. Il file di output ovviamente non deve essere "leggibile dall'uomo". Finché la codifica è senza perdita è valida.

Ecco un link a milioni di deck mischiati leggibili dall'uomo ( per riga). Chiunque può "esercitarsi" su un piccolo sottoinsieme di queste righe e quindi lasciarlo strappare sull'intero file. Continuerò ad aggiornare la mia dimensione di file migliore (più piccola) sulla base di questi dati.131

https://drive.google.com/file/d/0BweDAVsuCEM1amhsNmFITnEwd2s/view

A proposito, nel caso in cui tu sia interessato a quale tipo di gioco di carte vengono utilizzati questi dati, ecco il link alla mia domanda attiva (con una ricompensa di punti). Mi è stato detto che è un problema difficile da risolvere (esattamente) poiché richiederebbe un'enorme quantità di spazio di archiviazione dei dati. Diverse simulazioni concordano con le probabilità approssimative. Non sono state fornite (ancora) soluzioni puramente matematiche. È troppo difficile, immagino.300

/math/1882705/probability-2-player-card-game-with-multiple-patterns-to-win-who-has-the-advant

Ho un buon algoritmo che mostra bit per codificare il primo deck nei miei dati di esempio. Questi dati sono stati generati casualmente utilizzando l'algoritmo shuffle Fisher-Yates. Sono dati casuali reali, quindi il mio algoritmo appena creato sembra funzionare MOLTO bene, il che mi rende felice.168

Per quanto riguarda la "sfida" di compressione, sono attualmente a circa 160 bit per mazzo. Penso di poter scendere forse a 158. Sì, ci ho provato e ho ottenuto 158,43 bit per mazzo. Penso che mi sto avvicinando al limite del mio algoritmo, quindi sono riuscito a scendere al di sotto di 166 bit per mazzo ma non sono riuscito a ottenere 156 bit che sarebbero 3 bit per scheda ma è stato un esercizio divertente. Forse in futuro penserò a qualcosa per ridurre ogni mazzo in media di 2,43 bit o più.


8
Se stai generando tu stesso questi mazzi mischiati (piuttosto che descrivere lo stato di un mazzo di carte fisico, per esempio), non è necessario conservare il mazzo, basta conservare il seme RNG che ha generato il mazzo.
Jasonharper,

3
La tua descrizione e quelle delle risposte sono molto simili a un concetto comunemente noto come range encoding ( en.wikipedia.org/wiki/Range_encoding ). Adatti le propensioni dopo ogni carta in modo che rifletta le rimanenti carte possibili.
H. Idden,

I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Gilles 'SO- smetti di essere malvagio' il

Risposte:


3

Un'altra cosa da considerare: se ti interessa solo comprimere un set completo di diversi milioni di deck e non ti importa nemmeno in quale ordine si trovano, puoi ottenere ulteriore flessibilità di codifica scartando le informazioni sull'ordinamento del set di deck . Questo sarebbe il caso, ad esempio, se è necessario caricare il set per enumerare tutti i deck ed elaborarli, ma non importa in quale ordine vengono elaborati.

Si inizia codificando ciascun mazzo singolarmente, come altre risposte hanno descritto come fare. Quindi, ordina quei valori codificati. Memorizza una serie di differenze tra i valori codificati ordinati (dove la prima differenza inizia dal deck codificato '0'). Dato un gran numero di deck, le differenze tenderanno ad essere inferiori all'intervallo di codifica completo, quindi è possibile utilizzare una qualche forma di codifica varint per gestire occasionalmente grandi differenze pur conservando in modo efficiente le differenze minori. Lo schema varint appropriato dipenderà dal numero di deck presenti nel set (determinando così la dimensione della differenza media).

Sfortunatamente non conosco la matematica di quanto ciò possa aiutare la tua compressione, ma ho pensato che questa idea potesse essere utile da considerare.


1
In termini molto approssimativi, se hai diversi milioni di deck casuali, le differenze medie saranno uno (diversi milionesimi) dell'intero range, il che significa che ti aspetti di risparmiare circa 20 bit per valore. Si perde un po 'per la codifica varint.
Steve Jessop,

2
@DavidJames: se l'ordine specifico dei mazzi non è importante, solo che non ci sono pregiudizi in esso, potresti ri-mescolare i 3 milioni di mazzi dopo la decompressione (cioè non cambiare nessuno dei mazzi, basta cambiare l'ordine di l'elenco di 3 milioni di mazzi).
Steve Jessop,

2
Questo è solo un modo per ridurre ulteriormente il contenuto delle informazioni se le informazioni di ordinazione non sono importanti; se è importante, questo non è applicabile e può essere ignorato. Detto questo, se l'unica importanza per l'ordinamento del set di mazzi è che è 'casuale', puoi semplicemente randomizzare l'ordine dopo la decompressione, come affermato da @SteveJessop.
Dan Bryant,

@DavidJames Vedendo che i primi 173 dei tuoi mazzi iniziano con KKKK, e non guarda gli altri milioni, e concludere che iniziano tutti con KKKK, è una cosa piuttosto stupida da fare. Soprattutto se sono ovviamente ordinati.
user253751

3
@DavidJames: questi dati sono compressi e la routine di decompressione può ri-randomizzarli se lo si desidera. "Qualcuno ingenuo" non otterrà nulla, non capirà nemmeno come interpretarlo come un mazzo di carte. E ' non è un difetto in un formato di archiviazione dei dati (in questo caso un formato lossy), che chiunque usi ha bisogno di RTFM per ottenere i dati a destra fuori.
Steve Jessop,

34

Ecco un algoritmo completo che raggiunge il limite teorico.

Prologo: codifica di sequenze di numeri interi

Una sequenza di 13 numeri interi "numero intero con limite superiore , numero intero con limite superiore b - 1 ," numero intero con limite superiore c - 1 , numero intero con limite superiore d - 1 , ... numero intero con limite superiore m - 1 " può sempre essere codificato con la massima efficienza.a1b1c1d1m1

  1. Prendi il primo numero intero, moltiplicalo per , aggiungi il secondo, moltiplica il risultato per c , aggiungi il terzo, moltiplica il risultato per d , ... moltiplica il risultato per m , aggiungi il tredicesimo - e questo produrrà un numero univoco compreso tra 0 e a b c d e f g h i j k l m - 1 .bcdm0abcdefghijklm1
  2. Scrivi quel numero in binario.

Anche il contrario è facile. Dividi per il resto è il tredicesimo numero intero. Dividi il risultato per le lettere e il resto è il dodicesimo numero intero. Continuare fino a quando non si è diviso per b : il resto è il secondo numero intero e il quoziente è il primo numero intero.mlb

Quindi, per codificare le tue carte nel miglior modo possibile, tutto ciò che dobbiamo fare è trovare una corrispondenza perfetta tra sequenze di 13 numeri interi (con i limiti superiori indicati) e disposizioni delle tue carte mescolate.

Ecco come farlo.

Corrispondenza tra shuffle e sequenze di numeri interi

Inizia con una sequenza di 0 carte sul tavolo di fronte a te.

Passo 1

Prendi i quattro 2 nel tuo mazzo e mettili sul tavolo.

Che scelte hai? Una o più carte possono essere piazzate all'inizio della sequenza già sul tavolo o dopo una qualsiasi delle carte in quella sequenza. In tal caso, ciò significa che ci sono posti possibili per mettere le carte.1+0=1

Il numero totale di modi per posizionare 4 carte in 1 posto è . Codifica ciascuno di questi modi come un numero compreso tra 0 e 1 - 1 . C'è 1 di questi numeri.1011

Ho ottenuto 1 considerando i modi di scrivere 0 come la somma di 5 numeri interi: è .4×3×2×14!

Passo 2

Prendi i quattro 3 nel tuo mazzo e mettili sul tavolo.

Che scelte hai? Una o più carte possono essere piazzate all'inizio della sequenza già sul tavolo o dopo una qualsiasi delle carte in quella sequenza. In tal caso, ciò significa che ci sono posti possibili per mettere le carte.1+4=5

Il numero totale di modi per posizionare 4 carte in 5 posti è . Codifica ciascuno di questi modi come un numero compreso tra 0 e 70 - 1 . Ci sono 70 di questi numeri.700701

Ho ottenuto 70 considerando i modi di scrivere 4 come la somma di 5 numeri interi: è .8×7×6×54!

Passaggio 3

Prendi i quattro 4 nel tuo mazzo e mettili sul tavolo.

Che scelte hai? Una o più carte possono essere piazzate all'inizio della sequenza già sul tavolo o dopo una qualsiasi delle carte in quella sequenza. In tal caso, ciò significa che ci sono posti possibili per mettere le carte.1+8=9

Il numero totale di modi per posizionare 4 carte in 9 posti è . Codifica ciascuno di questi modi come un numero compreso tra 0 e 495 - 1 . Ci sono 495 tali numeri.49504951

Ho ottenuto 495 considerando i modi di scrivere 8 come la somma di 5 numeri interi: è .12×11×10×94!

E così via, fino a ...

Passaggio 13

Prendi i quattro assi nel tuo mazzo e mettili sul tavolo.

Che scelte hai? Una o più carte possono essere piazzate all'inizio della sequenza già sul tavolo o dopo una qualsiasi delle carte in quella sequenza. In tal caso, ciò significa che ci sono posti possibili per mettere le carte.1+48=49

Il numero totale di modi per posizionare 4 carte in 49 posti è . Codifica ciascuno di questi modi come un numero compreso tra 0 e 270725 - 1 . Ci sono 270725 di tali numeri.27072502707251

Ho ottenuto 270725 considerando i modi di scrivere 48 come la somma di 5 numeri interi: è .52×51×50×494!


Questa procedura produce una corrispondenza 1 a 1 tra (a) mischiature di carte in cui non ti interessa il seme e (b) sequenze di numeri interi in cui il primo è compreso tra e 1 - 1 , il secondo è compreso tra 0 e 70 - 1 , il terzo è compreso tra 0 e 495 - 1 , e così via fino al tredicesimo, che è compreso tra 0 e 270725 - 1 .01107010495102707251

Facendo riferimento a "Codifica di sequenze di numeri interi", puoi vedere che una tale sequenza di numeri interi è in corrispondenza di 1-1 con i numeri compresi tra e ( 1 × 70 × 495 × × 270725 ) - 1 . Se guardi l'espressione "prodotto diviso per un fattoriale" di ciascuno dei numeri interi ( come descritto in corsivo alla fine di ogni passaggio ) vedrai che questo significa che i numeri sono compresi tra 0 e 52 !0(1×70×495××270725)10che la mia risposta precedente aveva mostrato era la migliore possibile.

52!(4!)131,

Quindi abbiamo un metodo perfetto per comprimere le tue carte mescolate.


L'algoritmo

Calcola un elenco di tutti i modi di scrivere 0 come somma di 5 numeri interi, di scrivere 4 come somma di 5 numeri interi, di scrivere 8 come somma di 5 numeri interi, ... di scrivere 48 come somma di 5 numeri interi. L'elenco più lungo ha 270725 elementi, quindi non è particolarmente grande. (La precomputazione non è strettamente necessaria perché puoi facilmente sintetizzare ogni elenco come e quando ne hai bisogno: provando con Microsoft QuickBasic, anche passare attraverso l'elenco 270725 elementi era più veloce di quanto l'occhio potesse vedere)

Per passare da uno shuffle a una sequenza di numeri interi:

I 2 non contribuiscono a nulla, quindi ignoriamoli. Annota un numero compreso tra 0 e 1-1.

I 3: quanti 2 ci sono prima dei primi 3? Quanti prima del secondo? il terzo? il 4? dopo il 4? La risposta è 5 numeri interi che ovviamente sommano fino a 4. Quindi cerca quella sequenza di 5 numeri interi nella tua lista "scrivendo 4 come somma di 5 numeri interi" e annota la sua posizione in quella lista. Sarà un numero compreso tra 0 e 70-1. Scrivilo.

I 4: quanti 2 o 3 ci sono prima dei primi 4? Quanti prima del secondo? il terzo? il 4? dopo il 4? La risposta è 5 numeri interi che ovviamente sommano fino a 8. Quindi cerca quella sequenza di 5 numeri interi nella tua lista "scrivendo 8 come somma di 5 numeri interi" e annota la sua posizione in quella lista. Questo sarà un numero compreso tra 0 e 495-1. Scrivilo.

E così via, fino a ...

Gli assi: quante carte non-asso ci sono prima del primo asso? Quanti prima del secondo? il terzo? il 4? dopo il 4? La risposta è 5 numeri interi che ovviamente aggiungono fino a 48. Quindi cerca quella sequenza di 5 numeri interi nella tua lista "scrivendo 48 come somma di 5 numeri interi" e annota la sua posizione in quella lista. Sarà un numero compreso tra 0 e 270725-1. Scrivilo.

Ora hai scritto 13 numeri interi. Codificali (come precedentemente descritto) in un singolo numero compreso tra e 52 !0 . Scrivi quel numero in binario. Ci vorranno poco meno di 166 bit.52!(4!)13

Questa è la migliore compressione possibile, perché raggiunge il limite teorico delle informazioni.

La decompressione è semplice: passa dal grande numero alla sequenza di 13 numeri interi, quindi usali per costruire la sequenza di carte come già descritto.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
DW

Questa soluzione per me non è chiara e incompleta. Non mostra come ottenere effettivamente il numero di 166 bit e decodificarlo nel deck. Non è affatto facile concepire per me, quindi non so come implementarlo. La tua formula a gradini praticamente smonta il formula in 13 pezzi che non mi aiuta molto. Penso che sarebbe stato d'aiuto se tu avessi fatto un diagramma o un grafico per forse il passaggio 2 con i 70 modi possibili di disporre le carte. La tua soluzione è troppo astratta per essere accettata ed elaborata dal mio cervello. Preferisco esempi e illustrazioni reali. 52!/(4!13)13
David James,

23

Invece di provare a codificare ciascuna scheda separatamente in 3 o 4 bit, suggerisco di codificare lo stato dell'intero mazzo in 166 bit. Come spiega Martin Kochanski , ci sono meno di possibili disposizioni delle carte ignorando i semi, quindi ciò significa che lo stato dell'intero mazzo può essere memorizzato in 166 bit.2166

Come si esegue questa compressione e decompressione in modo algoritmico, in modo efficiente? Suggerisco di usare l'ordinamento lessicografico e la ricerca binaria. Ciò ti consentirà di eseguire la compressione e la decompressione in modo efficiente (sia nello spazio che nel tempo), senza richiedere una grande tabella di ricerca o altri presupposti non realistici.

Più in dettaglio: ordiniamo i mazzi usando l'ordinamento lessicografico sulla rappresentazione non compressa del mazzo, ovvero un mazzo è rappresentato in forma non compressa come una stringa come 22223333444455556666777788889999TTTTJJJJQQQQQKKKKAAAA; puoi ordinarli secondo l'ordine lessicografico. Ora, supponiamo di avere una procedura che ha dato un mazzo , conta il numero di mazzi che lo precedono (in ordine lessicografico). Quindi puoi usare questa procedura per comprimere un mazzo: dato un mazzo D , comprimi è un numero di 166 bit contando il numero di mazzi che lo precedono e quindi emettendo quel numero. Quel numero è la rappresentazione compressa del mazzo.DD

Per decomprimere, usa la ricerca binaria. Dato un numero , si vuole trovare il n ° ponte nell'ordinamento lessicografico di tutti i ponti. Puoi farlo usando una procedura lungo le linee della ricerca binaria: scegli un mazzo D 0 , conta il numero di mazzi prima di D 0 e confrontalo con n . Questo ti dirà se regolare D 0nnD0D0nD0venire prima o dopo. Ti suggerisco di provare a ottenere iterativamente il simbolo giusto: se vuoi recuperare una stringa come 22223333444455556666777788889999TTTTJJJJQQQQKKKKKAAAA, prima cerca per trovare cosa usare come primo simbolo nella stringa (prova tutte le 12 possibilità o usa la ricerca binaria sulle 12 possibilità ), quindi quando hai trovato il valore giusto per il primo simbolo, cerca il secondo simbolo e così via.

Non rimane che venire con una procedura efficace per contare il numero di mazzi che vengono lessicografico prima . Sembra un esercizio combinatorio semplice ma noioso. In particolare, ti suggerisco di creare una subroutine per il seguente problema: dato un prefisso (come 222234), conta il numero di deck che iniziano con quel prefisso. La risposta a questo problema sembra un esercizio abbastanza semplice in coefficienti binomiali e fattoriali. Quindi, è possibile richiamare questa subroutine un piccolo numero di volte per contare il numero di piattaforme che vengono prima D .DD


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
DW

8

Il numero di possibili arrangiamenti delle carte che ignorano i semi è cui logaritmo base 2 è 165.976, o 3.1919 bit per scheda, che è migliore del limite che hai dato.

52!(4!)13,

Qualsiasi "bit per carta" encoding fissa non ha senso, perché, come si nota, l'ultima carta può sempre essere codificato in bit e in molti casi le ultime poche carte possono essere pure. Ciò significa che per un certo modo verso la "coda" del mazzo il numero di bit necessari per ogni carta sarà molto meno di quanto si pensi.0

Il modo migliore per comprimere i dati sarebbe di gran lunga trovare 59 bit di altri dati che si desidera impacchettare comunque con i dati della propria scheda (59,6 bit, in realtà) e, scrivere quei 59 bit come un numero di 13 cifre modulo 24 (= ), Assegna un seme ad ogni carta (una cifra sceglie tra i 4 ! Modi di assegnare semi agli assi, un altro fa lo stesso per i re, e così via). Quindi hai un mazzo di 52 carte completamente distinte. 52 ! le possibilità possono essere codificate in 225,58 bit molto facilmente.4!4!52!

Ma farlo senza cogliere l'opportunità di codificare quei bit extra è anche possibile in una certa misura, e ci penserò come sono sicuro che lo siano tutti gli altri. Grazie per un problema davvero interessante!


1
Potrebbe essere usato un approccio simile al furto di testo cifrato ? Come in, i dati che codifichi in quei 59 bit extra sono gli ultimi 59 bit della rappresentazione codificata?
John Dvorak,

@JanD Stavo pensando di indagare su qualcosa del genere. Ma poi si è scoperto che esiste un algoritmo che raggiunge il limite teorico ed è semplice e affidabile al 100%, quindi non ha senso guardare oltre.
Martin Kochanski,

@MartinKochanski - Non lo definirei come "ignorare i semi" perché stiamo ancora onorando i 4 semi standard per grado. Una formulazione migliore potrebbe essere "Il numero di possibili distinte disposizioni del mazzo è" ...
David James,

3

Questo è un problema risolto da tempo.

Quando distribuisci un mazzo di 52 carte, ogni carta che distribuisci ha uno dei 13 gradi con probabilità conosciute. Le probabilità cambiano con ogni carta distribuita. Questo viene gestito in modo ottimale usando un'antica tecnica chiamata codifica aritmetica adattiva, un miglioramento della codifica di Huffman. Di solito questo è usato per probabilità note, immutabili, ma può anche essere usato per cambiare le probabilità. Leggi l'articolo di Wikipedia sulla codifica aritmetica:

https://en.wikipedia.org/wiki/Arithmetic_coding


Va bene, ma questo non risponde alla mia domanda se può avvicinarsi, abbinare o battere il limite di codifica dell'entropia teorica. Sembra che dal momento che ci sono n possibili deck ciascuno con probabilità 1 / n, quindi la codifica entropica è il limite e non possiamo fare di meglio (a meno che non "imbrogliamo" e diciamo al decodificatore qualcosa sui dati di input all'encoder in anticipo.
David James,

3

Sia DW che Martin Kochanski hanno già descritto algoritmi per costruire una biiezione tra offerte e numeri interi nell'intervallo , ma sembra che nessuno dei due abbia ridotto il problema nella sua forma più semplice. (Nota 1)[0,52!(4!)13)

Supponiamo di avere un ponte (parziale) descritto dalla lista ordinata , in cui un i è il numero di carte di tipo i . Nel PO, il mazzo iniziale è descritto da un elenco di 13 elementi, ciascuno con valore 4. Il numero di mescolanze distinte di tale mazzo èaaii

c(a)=(ai)!ai!

che è una semplice generalizzazione dei coefficienti binomiali, e infatti potrebbe essere provato semplicemente disponendo gli oggetti un tipo alla volta, come suggerito da Martin Kochanski. (Vedi sotto, nota 2)

Ora, per qualsiasi mazzo (parziale), possiamo selezionare una carta per mescolare una alla volta, usando qualsiasi per cui un i > 0 . Il numero di shuffle unici che iniziano con i èiai>0i

{0if ai=0c(a1,...,ai1,ai1,ai+1,...,an)if ai>0.

e con la formula sopra, abbiamo

c(a1,...,ai1,ai1,ai+1,...,an)=aic(a)ai

Possiamo quindi ricorrere (o iterare) attraverso il mazzo fino a quando lo shuffle è completo osservando che il numero di shuffle è corrispondente a un prefisso lessicograficamente più piccolo del prefisso fino a èi

c(a)j=1iajj=1naj

L'ho scritto in Python per illustrare l'algoritmo; Python è uno pseudocodice ragionevole come un altro. Si noti che la maggior parte dell'aritmetica implica una precisione estesa; i valori (che rappresentano l'ordinale del riordino) e n (il numero totale di riordini possibili per il rimanente mazzo parziale) sono entrambi i bignum a 166 bit. Per tradurre il codice in un'altra lingua, sarà necessario utilizzare una sorta di libreria bignum.kn

Inoltre, uso solo un elenco di numeri interi anziché i nomi delle carte e, a differenza dei suddetti calcoli, gli interi sono basati su 0.

Per codificare uno shuffle, camminiamo attraverso lo shuffle, accumulando in ogni punto il numero di shuffle che iniziano con una carta più piccola usando la formula sopra:

from math import factorial
T = factorial(52) // factorial(4) ** 13

def encode(vec):
    a = [4] * 13
    cards = sum(a)
    n = T
    k = 0
    for idx in vec:
        k += sum(a[:idx]) * n // cards
        n = a[idx] * n // cards
        a[idx] -= 1
        cards -= 1
    return k

La decodifica di un numero di 166 bit è l'inverso semplice. Ad ogni passo, abbiamo la descrizione di un mazzo parziale e un ordinale; dobbiamo saltare le mescolanze iniziando con carte più piccole di quella corrispondente all'ordinale, quindi calcoliamo in uscita la carta selezionata, la rimuoviamo dal mazzo rimanente e regoliamo il numero di mescolanze possibili con il prefisso selezionato:

def decode(k):
    vec = []
    a = [4] * 13
    cards = sum(a)
    n = T
    while cards > 0:
        i = cards * k // n
        accum = 0
        for idx in range(len(a)):
            if i < accum + a[idx]:
                k -= accum * n // cards
                n = a[idx] * n // cards
                a[idx] -= 1
                vec.append(idx)
                break
            accum += a[idx]
        cards -= 1
    return vec

Non ho fatto alcun tentativo reale per ottimizzare il codice sopra. L'ho eseguito sull'intero file 3mil.TXT, verificando che encode(decode(line))risultasse nella codifica originale; ci sono voluti poco meno di 300 secondi. (Sette delle righe sono visibili nel test online su ideone .) Riscrivere in un linguaggio di livello inferiore e ottimizzare la divisione (che è possibile) probabilmente ridurrebbe quel tempo a qualcosa di gestibile.

Poiché il valore codificato è semplicemente un numero intero, può essere emesso in 166 bit. Non vi è alcun valore nell'eliminazione degli zeri iniziali, dal momento che non ci sarebbe modo di sapere dove terminasse una codifica, quindi è davvero una codifica a 166 bit.

Tuttavia, vale la pena notare che in un'applicazione pratica, probabilmente non è mai necessario codificare uno shuffle; un shuffle casuale può essere generato generando un numero casuale di 166 bit e decodificandolo. E non è davvero necessario che tutti i 166 bit siano casuali; sarebbe possibile, ad esempio, iniziare con un numero intero casuale a 32 bit e quindi riempire i 166 bit usando qualsiasi RNG standard seminato con il numero a 32 bit. Quindi, se l'obiettivo è semplicemente quello di essere in grado di memorizzare in modo riproducibile un gran numero di shuffle casuali, è possibile ridurre il requisito di archiviazione per transazione più o meno arbitrariamente.

Se vuoi codificare un gran numero di affari reali (generati in qualche altro modo) ma non ti preoccupi dell'ordine delle offerte, puoi delta-codificare l'elenco ordinato di numeri, risparmiando circa 2 N bit di registro per ciascuno numero. (Il risparmio deriva dal fatto che una sequenza ordinata ha meno entropia di una sequenza non ordinata. Non riduce l'entropia di un singolo valore nella sequenza.)Nlog2N

Supponendo che sia necessario codificare un elenco ordinato di numeri k -bit, possiamo procedere come segue:N k

  1. Scegli come numero intero vicino al log 2 N (o il pavimento o il soffitto funzioneranno; di solito vado per il soffitto).plog2N

  2. Dividiamo implicitamente l'intervallo di numeri in intervalli di per prefisso binario. Ciascun numero k -bit è diviso in un prefisso p- bit e un suffisso k - p -bit; scriviamo solo i suffissi (in ordine). Ciò richiede N ( k - p ) bit.2pKpK-pN*(K-p)

  3. Inoltre, creiamo una sequenza di bit: per ciascuno dei prefissi p (tranne il prefisso 0 ) scriviamo uno 0 per ogni numero con quel prefisso (se presente) seguito da un 1 . Questa sequenza ha ovviamente 2 p + N bit: 2 p 1 s e N 0 s.2p0012p+N2p 1N 0

Per decodificare i numeri iniziamo un contatore di prefissi a 0 e procediamo a lavorare attraverso la sequenza di bit. Quando vediamo uno , produciamo il prefisso corrente e il suffisso successivo dall'elenco dei suffissi; quando vediamo un 1 , incrementiamo il prefisso corrente.01

La lunghezza totale della codifica è che è molto vicino a N * ( k - p ) + N + N , o N * ( k - p + 2 ) , per una media di k - p + 2 bit per valore.N*(K-p)+N+2pN*(K-p)+N+NN*(K-p+2)K-p+2

Appunti

  1. è92024242230271040357108320801872044844750000000000eregistra252!52!(4!)1392024242230271040357108320801872044844750000000000 è circa165.9765. Nel testo, ogni tanto faccio finta che il logaritmo di base 2 sia davvero166; nel caso di generazione di ordinali casuali all'interno dell'intervallo, potrebbe essere utilizzato un algoritmo di rifiuto che rifiuterebbe molto raramente un numero casuale generato.log252!(4!)13165.9765166
  2. Per comodità, scrivo per n i = k a i ; poi il un 1 oggetti di tipo 1 possono essere collocati in ( S 1SKΣio=Knun'ioun'11modi, e quindi gli oggetti di tipo2possono essere posizionati in(S2(S1un'1)2modi e così via. Da ( Si(S2un'2), che porta al conteggio totale(Sioun'io)=Sio!un'io!(Sio-un'io)!=Sio!un'io!Sio+1!

Πio=1nSio!Πio=1nun'io!Sio+1!

che semplifica la formula sopra.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
DW

@rici - Ti ho dato il premio +100 perché hai spiegato la tua risposta in quella che sembra una presentazione migliore, incluso il codice, mentre le altre risposte sono più astratte / teoriche, tralasciando alcuni dettagli su come implementare effettivamente la codifica / decodifica. Come forse saprai, ci sono molti dettagli durante la scrittura del codice. Ammetto che il mio algoritmo non è il più diretto, semplice, facile da capire, ma in realtà l'ho fatto funzionare senza troppi sforzi e nel tempo posso farlo funzionare più velocemente con una maggiore compressione. Quindi grazie per la risposta e continuate così.
David James,

2

Come soluzione alternativa a questo problema, il mio algoritmo utilizza bit frazionari composti (non interi) per carta per gruppi di carte nel mazzo in base al numero di ranghi rimasti vuoti. È un algoritmo piuttosto elegante. Ho controllato manualmente il mio algoritmo di codifica e sta andando bene. L'encoder sta emettendo quelle che sembrano essere stringhe di bit corrette (in forma di byte per semplicità).

La panoramica del mio algoritmo è che utilizza una combinazione di gruppi di carte e codifica di bit frazionata composta. Ad esempio, nel mio file di prova condiviso di milioni di mazzi mescolate, il primo ha le prime 7 carte di 54 A 236 J . Il motivo per cui ho scelto una dimensione di blocco di 7 carte quando sono possibili 13 gradi di carte è perché 13 7 "calzascarpe" (si adatta comodamente) in 26 bit (poiché 13 7 = 62 , 748 , 517 e 2 26 = 67 , 108 ,3754UN236J7131372613762,748,517226 ). Idealmente, vogliamo che quei 2 numeri siano il più vicino possibile (ma con la potenza di 2 numeri leggermente superiore), quindi non perdiamo più di una piccolissima frazione di bit nel processo di imballaggio dei bit. Nota: avrei potuto anche scegliere la dimensione di gruppo 4 durante la codifica di 13 gradi poiché 13 4 = 28 , 561 e 2 15 = 32 , 768 . Non è stretto una misura dal 15 / 4 = 3.75 ma 26 / 7 = 3.71467,108,864241313428,56121532,76815/4=3.7526/7=3.714. Così il numero di bit per carta è leggermente inferiore per ogni carta, se usiamo il metodo di imballaggio.26/7

Quindi, guardando , osserviamo semplicemente la posizione ordinale di quei ranghi nella nostra lista principale di ranghi ordinati " 23456789 T J Q K A ". Ad esempio, il primo rango di carta effettivo di 5 ha una posizione di ricerca nella stringa di ricerca di rango di 4 . Trattiamo queste 7 posizioni di rango come un numero di base 13 che inizia con 0 (quindi la posizione 4 che abbiamo ottenuto in precedenza sarà effettivamente un 3). Convertito alla base 10 (a scopo di controllo), otteniamo 15 , 565 , 975 . Nel 2654UN236J23456789TJQKUN547131015,565,97526bit di binario otteniamo .00111011011000010010010111

Il decodificatore funziona in modo molto simile. Prende (ad esempio) quella stringa di bit e la converte in decimale (base 10) per ottenere 15 , 565 , 975 , quindi la converte in base 13 per ottenere gli offset nella stringa di ricerca dei ranghi, quindi ricostruisce i ranghi uno alla volta e ottiene le prime 7 carte 54 A 236 J originali . Notare che la dimensione dei blocchi non sarà sempre 26 ma inizierà sempre a 26 in ciascun mazzo. L'encoder e il decoder hanno entrambi alcune informazioni importanti sui dati del deck anche prima che funzionino. Questa è una cosa eccezionalmente bella di questo algoritmo.2615,565,9751354UN236J7

Ogni ° ranghi rimanente (ad esempio ha una propria groupsize e il costo (# di bit per carta). Questi sono stati trovati sperimentalmente solo giocando con poteri di 13 , 12 , 11 ... e poteri di 2 . Ho già spiegato come ho ottenuto la dimensione del gruppo per quando possiamo vedere 13 gradi, quindi che ne dite quando scendiamo a 12 gradi non riempiti? Stesso metodo Guarda i poteri di 12 e fermati quando uno di loro si avvicina molto a un potere di 2 ma appena leggermente sotto di esso. 13,12,11...,2,1)13,12,11 ...21312122 = 248 , 832 e 2 18 = 262 , 144 . Questa è una misura abbastanza stretta. Il numero di bit che codificano questo gruppo è 18 / 5 = 3,6 . Nel 13 gruppo rango era 26 / 7 = 3.714 così come si può vedere, il numero di ranghi vacanti diminuisce (ranghi sono riempiendo come 5555 , 3333 ), il numero di bit per codificare le carte diminuisce.125248,832218262,14418/53.61326/73.71455553333

Ecco il mio elenco completo dei costi (n. Di bit per scheda) per tutti i possibili n. Di ranghi da visualizzare:

= 3.000 = 3 7 17 / 6 = 2.833 = 213    26/7=3.714=3  5/7
12    18/5=3.600=3  3/5
11      7/2=3.500=3  1/2
10    10/3=3.333=3  1/3
  9    16/5=3.200=3  1/5
  8      3/1=3.000=3
  7    17/6=2.833=2  5/6
  6    13/5=2.600=2  3/5
  5      7/3=2.333=2  1/3
  4      2/1=2.000=2
  3      5/3=1.667=1  2/3
1 0 / 1..4 = 0.0 = 0  2      1/1=1.000=1
  1      0/1..4=0.0=0

Come puoi vedere chiaramente, quando il numero di ranghi vuoti diminuisce (cosa che farà ogni mazzo), diminuisce anche il numero di bit necessari per codificare ogni carta. Potresti chiederti cosa succede se riempiamo un grado ma non abbiamo ancora un gruppo. Ad esempio, se le prime carte nel mazzo fossero 5 , 6 , 7 , 7 , 7 , 7 , K , cosa dovremmo fare? Facile, il K normalmente lascerebbe cadere l'encoder dalla modalità di codifica a 13 gradi alla modalità di codifica a 12 gradi. Tuttavia, poiché non abbiamo ancora riempito il primo blocco di 7 carte in 1375,6,7,7,7,7,KK1312713classifica la modalità di codifica, includiamo la in quel blocco per completarla. Ci sono pochissimi rifiuti in questo modo. Ci sono anche casi mentre stiamo cercando di riempire un blocco, il numero di ranghi riempiti aumenta di 2 o anche di più. Anche questo non è un problema poiché riempiamo il blocco nella modalità di codifica corrente, quindi riprendiamo nella nuova modalità di codifica che può essere 1 , 2 , 3 ... in meno o addirittura rimanere nella stessa modalità (come nel caso nel primo mazzo nel file di dati in quanto vi sono 3 blocchi completi nella modalità di codifica di 13 gradi). Questo è il motivo per cui è importante rendere ragionevoli le dimensioni dei blocchi, ad esempio tra le dimensioni 1 e 7K21,2,3 ...31317. Se lo rendessimo di dimensioni ad esempio, dovremmo riempire quel blocco con un bitrate più alto rispetto a se lasciamo che il codificatore passi a una modalità di codifica più efficiente (codificando meno ranghi).20

Quando ho eseguito questo algoritmo (a mano) sul primo mazzo di carte nel file di dati (che è stato creato usando shuffle imparziale Fisher-Yates), ho ottenuto un impressionante bit da codificare che è quasi identico alla codifica binaria ottimale ma non richiede conoscenza delle posizioni ordinali di tutti i mazzi possibili, nessun numero molto grande e nessuna ricerca binaria. Richiede tuttavia manipolazioni binarie e anche manipolazioni radix (potenze di 13 , 12 , 11 ...).16813,12,11

10777748747S. Se il mazzo termina su una coppia (come 77), triplo / set (come 777) o un quad (come 7777), otteniamo ulteriori risparmi per quel mazzo usando il mio algoritmo.

3222613163232

Nel primo mazzo nel file di dati, la codifica delle carte è la seguente (diagramma che verrà dopo). Il formato è (groupize, bit, modalità codifica rango):

7,26,1372613
7,26,13
7,26,13
5,18,12
5,18,12
3,10,10
3,  9,  8
6,17,  7
5,13,  6
3,  5,  3
1,  0,  1

521683.23

181/33.23.254545454722772277...322223333444455556666777788889999TTTTJJJJQQQQKKKKAAAA40

1103,7K8101carta rimanente. Questo è importante perché rende il processo di codifica più efficiente quando il decodificatore può fare ipotesi corrette senza che l'encoder debba passare messaggi extra ad esso.

313121110

         26             26             26            18         18       10      9          17           13        5     0
    54A236J  87726Q3  3969AAA  QJK7T  9292Q  36K  J57   T8TKJ4  48Q8T  55K  4
13                                            12                    xy     98         7              6        543     2 1  0

2166175168bit. Nota che alla fine del mazzo abbiamo ottenuto solo un 4, ma se invece avessimo lì tutti e quattro i 4, questo è un caso migliore e avremmo avuto bisogno solo di 161 bit per codificare quel mazzo, un caso in cui l'imballaggio batte effettivamente il entropia di una codifica binaria diritta della sua posizione ordinale.

Ora ho implementato il codice per calcolare i requisiti di bit e mi mostra in media, circa 175 bit per deck con un minimo di 155 e un massimo di 183 per il file di test di 3 milioni di deck. Quindi il mio algoritmo sembra usare 9 bit extra per deck rispetto alla codifica binaria diritta del metodo della posizione ordinale. Non male, è richiesto solo il 5,5% di spazio di archiviazione aggiuntivo. 176 bit sono esattamente 22 byte, quindi è un po 'meglio di 52 byte per deck. Il mazzo del caso migliore (non mostrato in 3 milioni di file di test del mazzo) è composto da 136 bit e il mazzo del caso peggiore (mostrato nel file di test 8206 volte), è 183 bit. L'analisi mostra che il caso peggiore è quando non otteniamo il primo quadrupolo fino a quando vicino alla (o alla) carta 40. Quindi, poiché la modalità di codifica vuole cadere rapidamente, siamo "bloccati" riempiendo blocchi (grandi quanto 7 carte) in un modalità di codifica bit superiore. Si potrebbe pensare che non ottenere alcun quadruplo fino a quando la carta 40 non sarebbe abbastanza rara usando un mazzo ben mischiato, ma il mio programma mi sta dicendo che è successo 321 volte nel file di test di 3 milioni di mazzi in modo da farlo circa 1 su ogni 9346 mazzi. Questo è più spesso che mi sarei aspettato. Potrei verificare questo caso e gestirlo con meno bit ma è così raro che non influirebbe abbastanza sui bit medi.

Anche qui c'è qualcos'altro di molto interessante. Se ordino il mazzo in base ai dati grezzi del mazzo, la lunghezza dei prefissi che ripetono un numero significativo di volte è solo circa la lunghezza 6 (come 222244). Tuttavia, con i dati compressi, tale lunghezza aumenta a circa 16. Ciò significa che se ordino i dati compressi, dovrei essere in grado di ottenere un risparmio significativo semplicemente indicando al decodificatore un prefisso a 16 bit e quindi emettere il resto dei deck (meno il prefisso ripetuto) che hanno lo stesso prefisso, quindi vai al prefisso successivo e ripeti. Supponendo di salvare anche solo 10 bit per deck in questo modo, dovrei battere i 166 bit per deck. Con la tecnica di enumerazione dichiarata da altri, non sono sicuro che il prefisso sarebbe lungo quanto il mio algoritmo. Anche la velocità di imballaggio e decompressione usando il mio algoritmo è sorprendentemente buona.

Per quanto riguarda il 2 ° livello di compressione in cui ordino le stringhe di output del mio algoritmo, quindi uso la codifica "differenza": un metodo molto semplice sarebbe codificare i 61.278 prefissi univoci a 16 bit che compaiono almeno due volte nei dati di output (e un massimo di 89 volte riportate) semplicemente come bit iniziale di 0 nell'output per indicare al decompressore di 2 ° livello che stiamo codificando un prefisso (come 0000111100001111) e quindi qualsiasi mazzo impacchettato con lo stesso prefisso seguirà con un 1 bit iniziale a indica la parte non prefissa del mazzo impaccato. Il numero medio di mazzi impaccati con lo stesso prefisso è di circa 49 per ciascun prefisso, esclusi i pochi che sono univoci (solo 1 mazzo ha quel particolare prefisso). Sembra che posso salvare circa 15 bit per mazzo usando questa semplice strategia (memorizzando i prefissi comuni una volta).

Dopo il 2 ° livello di compressione usando la codifica della differenza (prefisso) dell'uscita bittring ordinata del primo encoder, ora sto ottenendo circa 160 bit per deck. Uso il prefisso lunghezza 18 e lo conservo intatto. Poiché quasi tutti (245013 su 262144 = 93,5%) di quei possibili prefissi a 18 bit vengono visualizzati, sarebbe ancora meglio codificare i prefissi. Forse posso usare 2 bit per codificare il tipo di dati che ho. 00 = prefisso 18 di lunghezza normale memorizzato, 01 = "1 prefisso in alto" (uguale al prefisso precedente tranne 1 aggiunto), 11 = codifica diritta dall'imballaggio di 1 ° livello (circa 175 bit in media). 10 = espansione futura quando penso a qualcos'altro da codificare che salverà i bit.

Qualcun altro ha già battuto 160 bit per mazzo? Penso di poter ottenere il mio un po 'più in basso con alcuni esperimenti e l'uso dei descrittori a 2 bit che ho menzionato sopra. Forse andrà a fondo a 158ish. Il mio obiettivo è di portarlo a 156 bit (o meglio) perché sarebbe 3 bit per scheda o meno. Molto impressionante. Molti esperimenti per arrivare a quel livello perché se cambio la codifica di primo livello, devo ripetere il test quale è la migliore codifica di secondo livello e ci sono molte combinazioni da provare. Alcune modifiche apportate potrebbero essere utili per altri dati casuali simili, ma alcuni potrebbero essere distorti verso questo set di dati. Non ne sono sicuro, ma se ho la voglia di provare un altro set di dati da 3 milioni di deck per vedere cosa succede se ottengo risultati simili.

1050

Qualcuno ha qualche idea su come rendere il mio algoritmo migliore come gli altri casi che dovrei codificare che ridurrebbe in media i bit di archiviazione per ciascun deck? Chiunque?

Altre 2 cose: 1) Sono un po 'deluso dal fatto che più persone non abbiano votato a favore della mia soluzione che, sebbene non sia ottimale nello spazio, è comunque decente e abbastanza facile da implementare (ho fatto funzionare bene la mia). 2) Ho fatto un'analisi sul mio file di dati di 3 milioni di deck e ho notato che la carta che si verifica più frequentemente in cui si riempie il 1 ° grado (come 4444) si trova sulla carta 26. Ciò accade circa il 6,711% del tempo (per 201322 dei 3 milioni di mazzi ). Speravo di usare queste informazioni per comprimere di più come iniziare nella modalità di codifica a 12 simboli poiché sappiamo in media che non vedremo tutti i ranghi fino a circa il medio, ma questo metodo non è riuscito a comprimere poiché il sovraccarico ha superato i risparmi. Sto cercando alcune modifiche al mio algoritmo che possono effettivamente salvare bit.

Qualcuno ha qualche idea di cosa dovrei provare dopo per salvare qualche bit per mazzo usando il mio algoritmo? Sto cercando un modello che si verifichi abbastanza frequentemente in modo da poter ridurre i bit per deck anche dopo il sovraccarico aggiuntivo di dire al decoder quale modello aspettarsi. Stavo pensando a qualcosa con le probabilità attese delle rimanenti carte invisibili e raggruppando tutte le singole carte rimanenti in un singolo secchio. Questo mi permetterà di passare a una modalità di codifica inferiore più velocemente e forse di salvare alcuni bit, ma ne dubito.

Inoltre, FYI, ho generato 10 milioni di riordini casuali e li ho archiviati in un database per una facile analisi. Solo 488 di loro finiscono in un quad (come 5555). Se comprimo solo quelli che usano il mio algoritmo, ottengo in media 165.71712 bit con un minimo di 157 bit e un alto di 173 bit. Appena leggermente al di sotto dei 166 bit usando l'altro metodo di codifica. Sono un po 'sorpreso da quanto sia raro questo caso (circa 1 su 20.492 shuffles in media).


3
Ho notato che hai apportato circa 24 modifiche nell'arco di 9 ore. Apprezzo il tuo desiderio di migliorare la tua risposta. Tuttavia, ogni volta che modifichi la risposta, la porta all'inizio della prima pagina. Per questo motivo, scoraggiamo un editing eccessivo. Se prevedi di apportare molte modifiche, sarebbe possibile raggruppare le modifiche in modo da apportare una modifica ogni poche ore? (Per inciso, nota che inserire "EDIT:" e "UPDATE:" nella tua risposta è di solito uno stile scadente. Vedi meta.cs.stackexchange.com/q/657/755. )
DW

4
Questo non è il posto dove mettere rapporti sullo stato di avanzamento, aggiornamenti sullo stato o elementi del blog. Vogliamo risposte complete, non "presto" o "Ho una soluzione ma non descriverò di cosa si tratta".
DW

3
Se qualcuno è interessato, troverà la soluzione migliorata. Il modo migliore è attendere la risposta completa e pubblicarla successivamente. Se hai degli aggiornamenti farebbe un blog. Non lo incoraggio, ma se proprio devi (non vedo il motivo valido del perché) puoi scrivere un commento sotto il tuo post e unirlo in seguito. Ti incoraggio anche a eliminare tutti i commenti obsoleti e a incorporarli in una domanda senza soluzione di continuità: diventa difficile leggere tutto. Cerco di creare il mio algoritmo, diverso da quello presentato, ma non sono contento dei risultati - quindi non inserisco i parziali da modificare - la casella di risposta è per quelli completi.
Evil

3
@DavidJames, ho capito. Tuttavia, ciò non cambia ancora le nostre linee guida: per favore non apportare così tante modifiche. (Se desideri proporre miglioramenti al sito Web, sentiti libero di pubblicare un post sul nostro Computer Science Meta o su meta.stackexchange.com suggerendolo. Gli sviluppatori non leggono questo thread di commenti.) Nel frattempo, noi lavorare con il software che abbiamo e fare molte modifiche è scoraggiato perché porta la domanda in cima. A questo punto, limitarti a una modifica al giorno potrebbe essere una buona linea guida per cui scattare. Sentiti libero di usare editor offline o StackEdit se questo ti aiuta!
DW

3
Non sto votando la tua risposta per diversi motivi. 1) è inutilmente lungo e FAR troppo prolisso. Puoi ridurre drasticamente la sua presentazione. 2) ci sono risposte migliori pubblicate, che si sceglie di ignorare per motivi a mia insaputa. 3) chiedere della mancanza di voti è di solito una "bandiera rossa" per me. 4) Questo è rimasto costantemente in prima pagina a causa di un numero INSANE di modifiche.
Nicholas Mancuso,
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.