¿Se pueden comprimir los datos aleatorios de cartas sin juego para aproximarse, igualar o incluso superar el almacenamiento de codificación de entropía? ¿Si es así, cómo?


19

Tengo datos reales que estoy usando para un juego de cartas simulado. Solo me interesan las filas de las cartas, no los palos. Sin embargo, es un mazo de cartas estándar , por lo que solo hay de cada rango posible en el mazo. El mazo se baraja bien para cada mano, y luego envío todo el mazo a un archivo. Así que sólo hay 13 posibles símbolos en el archivo de salida que son 2,3,4,5,6,7,8,9, T, J, Q, K, A . ( T = rango diez). Entonces, por supuesto, podemos empaquetarlos con 4 bits por símbolo, pero luego estamos desperdiciando 3 de las 16 codificaciones posibles. Podemos hacerlo mejor si agrupamos 4 símbolos a la vez y luego los comprimimos, porque524132,3,4,5,6,7,8,9,T,J,Q,K,AT43164134 = 28,561 y eso puede encajar "bien" en 15 bits en lugar de 16 . El límite teórico de bitpacking es log ( 13 ) / log ( 2 ) = 3.70044 para datos con 13 símbolos aleatorios para cada tarjeta posible. Sin embargo, no podemos tener 52 reyes, por ejemplo, en este mazo. DEBEMOS tener solo 4 de cada rango en cada mazo, por lo que la codificación de entropía se reduce en aproximadamente medio bit por símbolo a aproximadamente 3.2 .

Ok, entonces esto es lo que estoy pensando. Estos datos no son totalmente al azar. Sabemos que hay 4 de cada rango, por lo que en cada bloque de 52 cartas (llámelo baraja barajada), podemos hacer varias suposiciones y optimizaciones. Uno de esos es que no tenemos que codificar la última tarjeta, porque sabremos cuál debería ser. Otro ahorro sería si terminamos en un solo rango; por ejemplo, si las últimas 3 cartas en el mazo son 777 , no tendríamos que codificarlas porque el decodificador estaría contando cartas hasta ese punto y vería que todos los otros rangos se han llenado, y asumirá el 3 " faltan "cartas son las 7 s.

Entonces, mi pregunta a este sitio es, ¿qué otras optimizaciones son posibles para obtener un archivo de salida aún más pequeño en este tipo de datos, y si los usamos, podemos superar la entropía teórica (simple) de 3.70044 bits por símbolo, o ¿incluso se acerca al límite de entropía final de aproximadamente 3.2 bits por símbolo en promedio? ¿Si es así, cómo?

Cuando uso un programa tipo ZIP (WinZip por ejemplo), solo veo una compresión 2:1 , que me dice que solo está haciendo un paquete 4 bits "perezoso" a 4 bits. Si "precomprimo" los datos usando mi propio bitpacking, parece que eso me gusta más, porque cuando lo ejecuto a través de un programa zip, obtengo un poco más 2:1 compresión 2: 1 . Lo que estoy pensando es, ¿por qué no hacer toda la compresión yo mismo (porque tengo más conocimiento de los datos que el programa Zip). Me pregunto si puedo superar el "límite" de entropía de log ( 13 ) / log ( 2 ) = 3.70044. Sospecho que puedo con los pocos "trucos" que mencioné y algunos más que probablemente pueda descubrir. El archivo de salida, por supuesto, no tiene que ser "legible por humanos". Mientras la codificación no tenga pérdidas, es válida.

Aquí hay un enlace a 3 millones de barajas barajadas legibles por humanos ( 1 por línea). Cualquiera puede "practicar" en un pequeño subconjunto de estas líneas y luego dejar que rasgue todo el archivo. Seguiré actualizando mi mejor tamaño de archivo (el más pequeño) según estos datos.

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

Por cierto, en caso de que esté interesado en qué tipo de juego de cartas se utilizan estos datos, aquí está el enlace a mi pregunta activa (con recompensa de puntos). Me dicen que es un problema difícil de resolver (exactamente) ya que requeriría una gran cantidad de espacio de almacenamiento de datos. Sin embargo, varias simulaciones concuerdan con las probabilidades aproximadas. No se han proporcionado soluciones puramente matemáticas (todavía). Es muy difícil, supongo.300

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

Tengo un buen algoritmo que muestra bits para codificar el primer mazo en mis datos de muestra. Estos datos se generaron aleatoriamente utilizando el algoritmo de barajado de Fisher-Yates. Son datos aleatorios reales, por lo que mi algoritmo recién creado parece estar funcionando MUY bien, lo que me hace feliz.168

Con respecto al "desafío" de la compresión, actualmente estoy a unos 160 bits por plataforma. Creo que puedo bajar a quizás 158. Sí, lo intenté y obtuve 158.43 bits por cubierta. Creo que me estoy acercando al límite de mi algoritmo, así que logré caer por debajo de 166 bits por mazo, pero no pude obtener 156 bits, que serían 3 bits por tarjeta, pero fue un ejercicio divertido. Quizás en el futuro piense en algo para reducir cada mazo en promedio en 2.43 bits o más.


8
Si está generando estos mazos barajados usted mismo (en lugar de describir el estado de un mazo de cartas físico, por ejemplo), no necesita almacenar el mazo en absoluto, solo almacene la semilla RNG que generó el mazo.
jasonharper

3
Su descripción y las de las respuestas son muy similares a un concepto comúnmente conocido como codificación de rango ( en.wikipedia.org/wiki/Range_encoding ). Adapta las posibilidades de propagación después de cada carta para que refleje las cartas posibles restantes.
H. Idden

Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
Gilles 'SO- deja de ser malvado'

Respuestas:


3

Otra cosa a tener en cuenta: si solo le importa comprimir un conjunto completo de varios millones de mazos y tampoco le importa en qué orden están, puede obtener flexibilidad de codificación adicional al descartar la información sobre el orden del conjunto de mazos . Este sería el caso, por ejemplo, si necesita cargar el conjunto para enumerar todas las cubiertas y procesarlas, pero no le importa en qué orden se procesan.

Comienza codificando cada mazo individualmente, ya que otras respuestas han descrito cómo hacerlo. Luego, ordena esos valores codificados. Almacene una serie de diferencias entre los valores codificados ordenados (donde la primera diferencia comienza desde la plataforma codificada '0'). Dada una gran cantidad de mazos, las diferencias tenderán a ser más pequeñas que el rango de codificación completo, por lo que puede usar alguna forma de codificación varint para manejar grandes diferencias ocasionales y al mismo tiempo almacenar las diferencias más pequeñas de manera eficiente. El esquema de varint apropiado dependerá de la cantidad de mazos que tenga en el conjunto (determinando así el tamaño de diferencia promedio).

Desafortunadamente, no sé las matemáticas de cuánto esto ayudaría a su compresión, pero pensé que esta idea podría ser útil para considerar.


1
En términos generales, si tiene varios millones de mazos aleatorios, las diferencias promedio serán una (varias millonésimas) del rango completo, lo que significa que espera ahorrar alrededor de 20 bits por valor. Pierdes un poco por tu codificación varint.
Steve Jessop

2
@DavidJames: si el orden específico de los mazos no es importante, solo para que no haya sesgo, podría volver a barajar los 3 millones de mazos después de la descompresión (es decir, no cambie ninguno de los mazos, simplemente cambie el orden de los mazos). la lista de 3 millones de mazos).
Steve Jessop

2
Esta es solo una forma de reducir un poco más el contenido de la información si la información de pedido no es importante; si es importante, esto no es aplicable y puede ignorarse. Dicho esto, si la única importancia para ordenar el conjunto de mazos es que es 'aleatorio', puede simplemente aleatorizar el orden después de la descompresión, como dijo @SteveJessop.
Dan Bryant

@DavidJames Ver que los primeros 173 de tus mazos comienzan con KKKK, y no mira a los otros millones, y concluir que todos comienzan con KKKK, es algo bastante estúpido. Especialmente si obviamente están en un orden ordenado.
user253751

3
@DavidJames: estos datos están comprimidos y la rutina de descompresión puede volver a aleatorizarlos si lo desea. "Una persona ingenua" no obtendrá nada en absoluto, ni siquiera descubrirá cómo interpretarlo como barajas de cartas. Es no un defecto en un formato de almacenamiento de datos (en este caso un formato con pérdida), que alguien que use necesita RTFM para obtener los datos correctos a cabo.
Steve Jessop

34

Aquí hay un algoritmo completo que alcanza el límite teórico.

Prólogo: codificación de secuencias enteras

Una secuencia de 13 enteros "entero con límite superior , entero con límite superior b - 1 ", entero con límite superior c - 1 , entero con límite superior d - 1 , ... entero con límite superior m - 1 " siempre se puede codificar con perfecta eficiencia.a1b1c1d1m1

  1. Tome el primer entero, multiplíquelo por , agregue el segundo, multiplique el resultado por c , agregue el tercero, multiplique el resultado por d , ... multiplique el resultado por m , agregue el decimotercero, y eso producirá un número único entre 0 y a b c d e f g h i j k l m - 1 .bcdm0abcdefghijklm1
  2. Escribe ese número en binario.

Lo contrario también es fácil. Dividir por el resto es el decimotercer número entero. Divida el resultado entre l y el resto es el duodécimo entero. Continúe hasta que haya dividido entre b : el resto es el segundo entero y el cociente es el primer entero.metrolsi

Entonces, para codificar sus tarjetas de la mejor manera posible, todo lo que tenemos que hacer es encontrar una correspondencia perfecta entre las secuencias de 13 enteros (con límites superiores dados) y los arreglos de sus tarjetas barajadas.

Aquí está cómo hacerlo.

Correspondencia entre barajar y secuencias enteras

Comience con una secuencia de 0 cartas en la mesa frente a usted.

Paso 1

Tome los cuatro 2 en su paquete y colóquelos sobre la mesa.

Que opciones tienes Se puede colocar una carta o cartas al principio de la secuencia que ya está en la mesa, o después de cualquiera de las cartas en esa secuencia. En ese caso, esto significa que hay lugares posibles para colocar cartas.1+0 0=1

El número total de formas de colocar 4 cartas en 1 lugares es . Codifique cada una de esas formas como un número entre 0 y 1 - 1 . Hay 1 de esos números.10 01-1

Obtuve 1 al considerar las formas de escribir 0 como la suma de 5 enteros: es .4×3×2×14!

Paso 2

Tome los cuatro 3 en su paquete y colóquelos sobre la mesa.

¿Qué opciones tienes? Se puede colocar una carta o cartas al principio de la secuencia que ya está en la mesa, o después de cualquiera de las cartas en esa secuencia. En ese caso, esto significa que hay lugares posibles para colocar cartas.1+4=5

El número total de formas de colocar 4 cartas en 5 lugares es . Codificar cada una de esas formas como un número entre 0 y 70 - 1 . Hay 70 de esos números.700701

Obtuve 70 al considerar las formas de escribir 4 como la suma de 5 enteros: es .8×7×6×54!

Paso 3

Tome los cuatro 4 en su paquete y colóquelos sobre la mesa.

¿Qué opciones tienes? Se puede colocar una carta o cartas al principio de la secuencia que ya está en la mesa, o después de cualquiera de las cartas en esa secuencia. En ese caso, esto significa que hay lugares posibles para colocar cartas.1+8=9

El número total de formas de colocar 4 cartas en 9 lugares es . Codifique cada una de esas formas como un número entre 0 y 495 - 1 . Hay 495 de esos números.49504951

Obtuve 495 al considerar las formas de escribir 8 como la suma de 5 enteros: es .12×11×10×94!

Y así sucesivamente, hasta que ...

Paso 13

Tome los cuatro ases en su paquete y colóquelos sobre la mesa.

¿Qué opciones tienes? Se puede colocar una carta o cartas al principio de la secuencia que ya está en la mesa, o después de cualquiera de las cartas en esa secuencia. En ese caso, esto significa que hay lugares posibles para colocar cartas.1+48=49

El número total de formas de colocar 4 cartas en 49 lugares es . Codificar cada una de esas formas como un número entre 0 y 270.725 - 1 . Hay 270725 tales números.27072502707251

Obtuve 270725 al considerar las formas de escribir 48 como la suma de 5 enteros: es .52×51×50×494!


Este procedimiento produce una correspondencia 1 a 1 entre (a) barajas de cartas donde no te importa el palo y (b) secuencias de enteros donde el primero está entre y 1 - 1 , el segundo está entre 0 y 70 - 1 , el tercero está entre 0 y 495 - 1 , y así hasta el decimotercero, que está entre 0 y 270725 - 1 .01107010495102707251

Refiriéndose a "Codificación de secuencias enteras", puede ver que dicha secuencia de enteros está en correspondencia 1-1 con los números entre y ( 1 × 70 × 495 × × 270725 ) - 1 . Si observa la expresión "producto dividido por un factorial" de cada uno de los enteros ( como se describe en cursiva al final de cada paso ), verá que esto significa los números entre 0 y 52 .0(1×70×495××270725)10que mostró mi respuesta anterior fue el mejor posible.

52!(4!)131,

Así que tenemos un método perfecto para comprimir tus cartas barajadas.


El algoritmo

Precalcule una lista de todas las formas de escribir 0 como la suma de 5 enteros, de escribir 4 como la suma de 5 enteros, de escribir 8 como la suma de 5 enteros, ... de escribir 48 como la suma de 5 enteros. La lista más larga tiene 270725 elementos, por lo que no es particularmente grande. (La precomputación no es estrictamente necesaria porque puede sintetizar fácilmente cada lista cuando lo necesite: probar con Microsoft QuickBasic, incluso pasar por la lista de 270725 elementos fue más rápido de lo que el ojo podía ver)

Para pasar de una combinación aleatoria a una secuencia de enteros:

Los 2 no aportan nada, así que ignorémoslos. Escribe un número entre 0 y 1-1.

Los 3: ¿Cuántos 2 hay antes de los primeros 3? ¿Cuántos antes del segundo? ¿El tercero? el 4to? después del 4to? La respuesta es 5 números enteros que obviamente suman 4. Entonces, busca esa secuencia de 5 números enteros en tu lista de "escribir 4 como la suma de 5 números enteros" y anota su posición en esa lista. Ese será un número entre 0 y 70-1. Escríbelo.

Los 4: ¿Cuántos 2 o 3 hay antes de los primeros 4? ¿Cuántos antes del segundo? ¿El tercero? el 4to? después del 4to? La respuesta es 5 números enteros que obviamente suman 8. Entonces, busca esa secuencia de 5 números enteros en tu lista de "escribir 8 como la suma de 5 números enteros" y anota su posición en esa lista. Ese será un número entre 0 y 495-1. Escríbelo.

Y así sucesivamente, hasta que ...

Los ases: ¿Cuántas cartas que no son as hay antes del primer as? ¿Cuántos antes del segundo? ¿El tercero? el 4to? después del 4to? La respuesta es 5 números enteros que obviamente suman 48. Así que busque esa secuencia de 5 números enteros en su lista de "escribir 48 como la suma de 5 números enteros", y observe su posición en esa lista. Ese será un número entre 0 y 270725-1. Escríbelo.

Ahora ha escrito 13 enteros. ¡Codifíquelos (como se describió anteriormente) en un solo número entre y 52 !0 . Escribe ese número en binario. Tomará un poco menos de 166 bits.52!(4!)13

Esta es la mejor compresión posible, porque alcanza el límite teórico de la información.

La descompresión es sencilla: vaya del número grande a la secuencia de 13 enteros y luego úselos para construir la secuencia de tarjetas como ya se describió.


Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
DW

Esta solución no está clara para mí e incompleta. No muestra cómo obtener el número de 166 bits y decodificarlo nuevamente en la plataforma. No es nada fácil concebirme, así que no sé cómo implementarlo. ¡Su fórmula escalonada básicamente solo desmonta los fórmula en 13 piezas que realmente no me ayuda mucho. Creo que habría ayudado si hicieras un diagrama o gráfico para quizás el paso 2 con las 70 formas posibles de organizar las tarjetas. Su solución es demasiado abstracta para que mi cerebro la acepte y la procese. Prefiero ejemplos e ilustraciones reales. 52!/(4!13)13
David James

23

En lugar de intentar codificar cada tarjeta por separado en 3 o 4 bits, le sugiero que codifique el estado de toda la baraja en 166 bits. Como explica Martin Kochanski , hay menos de arreglos posibles de las cartas que ignoran los palos, por lo que eso significa que el estado de toda la baraja se puede almacenar en 166 bits.2166

¿Cómo se hace esta compresión y descompresión algorítmicamente, de manera eficiente? Sugiero usar el orden lexicográfico y la búsqueda binaria. Esto le permitirá realizar la compresión y descompresión de manera eficiente (tanto en espacio como en tiempo), sin requerir una gran tabla de búsqueda u otros supuestos poco realistas.

Más detalladamente: ordenemos mazos usando el orden lexicográfico en la representación sin comprimir del mazo, es decir, un mazo se representa en forma sin comprimir como una cadena como 22223333444455556666777788889999TTTTJJJJQQQQKKKKAAAA; Puede ordenarlos según el orden lexicográfico. Ahora, suponga que tiene un procedimiento que, dado un mazo , cuenta el número de mazos que lo precedieron (en orden lexicográfico). Luego puede usar este procedimiento para comprimir una plataforma: dado una plataforma D , comprime es a un número de 166 bits contando el número de plataformas que vienen antes y luego emitiendo ese número. Ese número es la representación comprimida de la baraja.DD

Para descomprimir, use la búsqueda binaria. Dado un número , que desea encontrar el n º cubierta en el orden lexicográfico de todas las cubiertas. Puede hacer esto usando un procedimiento a lo largo de las líneas de búsqueda binaria: elija un mazo D 0 , cuente el número de mazos antes de D 0 y compárelo con n . Eso le dirá si ajustar D 0nnD0D0nD0venir antes o después Le sugiero que intente obtener el símbolo de forma iterativa: si desea recuperar una cadena como 22223333444455556666777788889999TTTTJJJJQQQQKKKKAAAA, primero busque para encontrar qué usar como primer símbolo en la cadena (solo pruebe las 12 posibilidades, o use la búsqueda binaria en las 12 posibilidades ), luego, cuando haya encontrado el valor correcto para el primer símbolo, busque el segundo símbolo, y así sucesivamente.

Todo lo que queda es para llegar a un procedimiento eficaz para contar el número de barajas que vienen antes lexicográfico . Esto parece un ejercicio combinatorio sencillo pero tedioso. En particular, le sugiero que cree una subrutina para el siguiente problema: dado un prefijo (como 222234), cuente el número de mazos que comienzan con ese prefijo. La respuesta a este problema parece un ejercicio bastante fácil en coeficientes binomiales y factoriales. A continuación, puede invocar esta subrutina un pequeño número de veces para contar el número de barajas que se presentan ante D .DD


Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
DW

8

¡El número de arreglos posibles de las cartas que ignoran los palos es cuyo logaritmo base 2 es 165.976, o 3.1919 bits por tarjeta, que es mejor que el límite que le diste.

52!(4!)13,

Cualquier "bits por tarjeta de" codificación fija no tendrá sentido porque, como se nota, la última carta siempre puede ser codificado en bits, y en muchos casos los últimos pocos tarjetas pueden ser así. Eso significa que durante un buen camino hacia la "cola" del paquete, el número de bits necesarios para cada tarjeta será bastante menor de lo que piensas.0

Con mucho, la mejor manera de comprimir los datos sería encontrar 59 bits de otros datos que desee empaquetar con los datos de su tarjeta de todos modos (59.6 bits, en realidad) y escribir esos 59 bits como un módulo de número de 13 dígitos 24 (= ), asigne un traje para cada tarjeta (uno elige entre los dígitos 4 ! formas de asignar los trajes de los ases, otra hace lo mismo para los reyes, y así sucesivamente). Entonces tienes un paquete de 52 cartas completamente distintas. 52 ! las posibilidades se pueden codificar en 225.58 bits de manera muy fácil.4!4!52!

Pero hacerlo sin aprovechar la oportunidad de codificar esos bits adicionales también es posible hasta cierto punto, y lo pensaré como estoy seguro de que todos los demás lo son. ¡Gracias por un problema realmente interesante!


1
¿Podría usarse aquí un enfoque similar al robo de texto cifrado ? Como en, ¿los datos que codifica en esos 59 bits adicionales son los últimos 59 bits de la representación codificada?
John Dvorak

@ Jan Estaba pensando en investigar algo como esto. Pero luego resultó que existe un algoritmo que alcanza el límite teórico y es sencillo y 100% confiable, por lo que no tenía sentido buscar más.
Martin Kochanski

@MartinKochanski - No lo diría como "ignorar trajes" porque todavía estamos honrando los 4 trajes estándar por rango. Una mejor redacción podría ser "El número de posibles arreglos distintos del mazo es" ...
David James

3

Este es un problema resuelto hace mucho tiempo.

Cuando repartes un mazo de 52 cartas, cada carta que repartes tiene uno de hasta 13 rangos con probabilidades conocidas. Las probabilidades cambian con cada carta repartida. Esto se maneja de manera óptima utilizando una técnica antigua llamada codificación aritmética adaptativa, una mejora de la codificación Huffman. Por lo general, esto se usa para probabilidades conocidas e inmutables, pero también puede usarse para cambiar las probabilidades. Lea el artículo de Wikipedia sobre codificación aritmética:

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


De acuerdo, pero esto no responde a mi pregunta si puede acercarse, igualar o superar el límite de codificación de entropía teórica. Parece que dado que hay n decks posibles, cada uno con 1 / n de probabilidad, la codificación de entropía es el límite y no podemos hacerlo mejor (a menos que "engañemos" y le digamos al decodificador algo sobre los datos de entrada al codificador con anticipación).
David James

3

Tanto DW como Martin Kochanski ya han descrito algoritmos para construir una biyección entre ofertas y enteros en el rango , pero parece que ninguno de ellos ha reducido el problema a su forma más simple. (Nota 1)[0,52!(4!)13)

Supongamos que tenemos un mazo (parcial) descrito por la lista ordenada , donde a i es el número de cartas del tipo i . En el OP, el mazo inicial se describe mediante una lista de 13 elementos, cada uno de ellos con valor 4. El número de barajamientos distintos de dicho mazo esaaii

c(a)=(ai)!ai!

que es una simple generalización de los coeficientes binomiales, y de hecho podría probarse simplemente organizando los objetos de un tipo a la vez, como lo sugiere Martin Kochanski. (Ver abajo, nota 2)

Ahora, para cualquier mazo (parcial) de este tipo, podemos seleccionar una baraja baraja de una en una, utilizando cualquier para la cual a i > 0 . El número de barajaduras únicas que comienzan con i esiai>0i

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

y por la fórmula anterior, tenemos

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

Luego podemos recurrir (o iterar) a través del mazo hasta que se complete la barajadura observando que el número de barajaduras correspondiente a un prefijo lexicográficamente más pequeño que el prefijo hasta esi

c(a)j=1iajj=1naj

Escribí esto en Python para ilustrar el algoritmo; Python es un pseudocódigo tan razonable como cualquiera. Tenga en cuenta que la mayor parte de la aritmética implica precisión extendida; los valores (que representan el ordinal de la barajadura) yn (el número total de barajaduras posibles para la baraja parcial restante) son bignums de 166 bits. Para traducir el código a otro idioma, será necesario usar algún tipo de biblioteca bignum.kn

Además, solo uso la lista de enteros en lugar de los nombres de las tarjetas y, a diferencia de las matemáticas anteriores, los enteros están basados ​​en 0.

Para codificar un shuffle, caminamos a través del shuffle, acumulando en cada punto el número de shuffle que comienzan con una carta más pequeña usando la fórmula anterior:

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

Decodificar un número de 166 bits es el inverso simple. En cada paso, tenemos la descripción de un mazo parcial y un ordinal; necesitamos saltar las barajas comenzando con cartas más pequeñas que la que corresponde al ordinal, y luego calculamos la salida de la carta seleccionada, la eliminamos del mazo restante y ajustamos el número de barajas posibles con el prefijo seleccionado:

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

No hice ningún intento real para optimizar el código anterior. Lo ejecuté contra todo el archivo 3mil.TXT, comprobando que encode(decode(line))resultó en la codificación original; tomó poco menos de 300 segundos. (Se pueden ver siete de las líneas en la prueba en línea sobre ideona ). Reescribir en un lenguaje de nivel inferior y optimizar la división (que es posible) probablemente reduciría ese tiempo a algo manejable.

Como el valor codificado es simplemente un número entero, se puede generar en 166 bits. No hay ningún valor en eliminar los ceros iniciales, ya que no habría forma de saber dónde terminó una codificación, por lo que en realidad es una codificación de 166 bits.

Sin embargo, vale la pena señalar que en una aplicación práctica, probablemente nunca sea necesario codificar una combinación aleatoria; Se puede generar un aleatorio aleatorio generando un número aleatorio de 166 bits y decodificándolo. Y no es realmente necesario que los 166 bits sean aleatorios; sería posible, por ejemplo, comenzar con un entero aleatorio de 32 bits y luego completar los 166 bits utilizando cualquier RNG estándar sembrado con el número de 32 bits. Por lo tanto, si el objetivo es simplemente poder almacenar de forma reproducible una gran cantidad de aleatorios aleatorios, puede reducir el requisito de almacenamiento por acuerdo de manera más o menos arbitraria.

Si desea codificar un gran número de ofertas reales (generadas de alguna otra manera) pero no le importa el orden de las ofertas, puede codificar delta la lista ordenada de números, ahorrando aproximadamente 2 N bits de registro para cada número. (El ahorro se debe al hecho de que una secuencia ordenada tiene menos entropía que una secuencia no ordenada. No reduce la entropía de un solo valor en la secuencia).Nlog2N

Suponiendo que necesitamos para codificar una lista ordenada de k números -bit, podemos proceder como sigue:N k

  1. Elija como un número entero cerca del log 2 N (el piso o el techo funcionarán; generalmente voy por el techo).plog2N

  2. Dividimos implícitamente el rango de números en intervalos de por prefijo binario. Cada k número bits se divide en una p bits prefijo y un k - p bits sufijo; escribimos solo los sufijos (en orden). Esto requiere N ( k - p ) bits.2pkpkpN(kp)

  3. Además, creamos una secuencia de bits: para cada uno de los prefijos p (excepto el prefijo 0 ) escribimos un 0 para cada número con ese prefijo (si lo hay) seguido de un 1 . Obviamente, esta secuencia tiene 2 p + N bits: 2 p 1 sy N 0 s.2p0012p+N2p 1N 0

Para decodificar los números, comenzamos un contador de prefijos en 0 y procedemos a trabajar a través de la secuencia de bits. Cuando vemos un , sacamos el prefijo actual y el siguiente sufijo de la lista de sufijos; cuando vemos un 1 , incrementamos el prefijo actual.01

La longitud total de la codificación es que está muy cerca de N ( k - p ) + N + N , o N ( k - p + 2 ) , para un promedio de k - p + 2 bits por valor.N(kp)+N+2pN(kp)+N+NN(kp+2)kp+2

Notas

  1. es92024242230271040357108320801872044844750000000000, ylog252!52!(4!)1392024242230271040357108320801872044844750000000000 es aproximadamente165.9765. En el texto, ocasionalmente pretendo que el logaritmo de base 2 es realmente166; en el caso de generar ordinales aleatorios dentro del rango, se podría usar un algoritmo de rechazo que muy raramente rechazaría un número aleatorio generado.log252!(4!)13165.9765166
  2. Por conveniencia, escribo para n i = k a i ; entonces el un 1 objetos de tipo 1 se pueden colocar en ( S 1Ski=knaia11formas, y luego los objetos de tipo2se pueden colocar en(S2(S1a1)2formas, y así sucesivamente. Desde ( Si(S2a2), eso lleva al recuento total(Siai)=Si!ai!(Siai)!=Si!ai!Si+1!

i=1nSi!i=1nai!Si+1!

que se simplifica a la fórmula anterior.


Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
DW

@rici: le di la recompensa de +100 porque explicó su respuesta en lo que parece una mejor presentación, incluido el código, mientras que las otras respuestas son más abstractas / teóricas, dejando de lado algunos detalles sobre cómo implementar realmente la codificación / decodificación. Como ya sabrás, hay muchos detalles al escribir código. Admito que mi algoritmo no es el más sencillo, simple y fácil de entender, pero en realidad lo hice funcionar sin mucho esfuerzo y con el tiempo puedo hacerlo funcionar más rápido con más compresión. Así que gracias por su respuesta y sigan con el buen trabajo.
David James

2

Como una solución alternativa a este problema, mi algoritmo utiliza bits fraccionales compuestos (no enteros) por tarjeta para grupos de cartas en el mazo en función de cuántos rangos restantes quedan. Es un algoritmo bastante elegante. Revisé mi algoritmo de codificación a mano y se ve bien. El codificador está emitiendo lo que parecen ser cadenas de bits correctas (en forma de byte para simplificar).

La visión general de mi algoritmo es que utiliza una combinación de grupos de tarjetas y codificación de bits fraccionales compuestos. Por ejemplo, en mi archivo de prueba compartida de millones de mazos barajados, el primero tiene los primeros 7 tarjetas de 54 A 236 J . La razón por la que elegí un tamaño de bloque de 7 cartas cuando son posibles 13 filas de cartas es porque 13 7 "calzados" (se ajustan perfectamente) en 26 bits (ya que 13 7 = 62 , 748 , 517 y 2 26 = 67 , 108 ,3754A236J7131372613762,748,517226 ). Idealmente, queremos que esos 2 números estén lo más cerca posible (pero con la potencia del número 2 ligeramente más alto) para que no desperdiciemos más que una fracción muy pequeña de un bit en el proceso de empaquetado de bits. Tenga en cuenta que también podría haber elegido el tamaño de grupo 4 al codificar 13 rangos ya que 13 4 = 28 , 561 y 2 15 = 32 , 768 . No es tan apretado un ajuste desde 15 / 4 = 3,75 pero 26 / 7 = 3,71467,108,864241313428,56121532,76815/4=3.7526/7=3.714. Por lo que el número de bits por tarjeta es ligeramente inferior por tarjeta si usamos el método de embalaje.26/7

Entonces, mirando , simplemente buscamos la posición ordinal de esos rangos en nuestra lista maestra " 23456789 T J Q K A " de rangos ordenados. Por ejemplo, el primer rango de tarjeta real de 5 tiene una posición de búsqueda en la cadena de búsqueda de rango de 4 . Simplemente tratamos estas 7 posiciones de rango como un número base 13 que comienza con 0 (por lo que la posición 4 que obtuvimos anteriormente en realidad será un 3). Convertido de nuevo a la base 10 (para fines de verificación), obtenemos 15 , 565 , 975 . En 2654A236J23456789TJQKA547131015,565,97526 bits of binary we get 00111011011000010010010111.

The decoder works in a very similar way. It takes (for example) that string of 26 bits and converts it back to decimal (base 10) to get 15,565,975, then converts it to base 13 to get the offsets into the rank lookup string, then it reconstructs the ranks one at a time and gets the original 54A236J first 7 cards. Note that the blocksize of bits wont always be 26 but will always start out at 26 in each deck. The encoder and decoder both have some important information about the deck data even before they operate. That is one exceptionally nice thing about this algorithm.

Each # of ranks remaining (such as 13,12,11...,2,1) has its own groupsize and cost (# of bits per card). These were found experimentally just playing around with powers of 13,12,11... and powers of 2. I already explained how I got the groupsize for when we can see 13 ranks, so how about when we drop to 12 unfilled ranks? Same method. Look at the powers of 12 and stop when one of them comes very close to a power of 2 but just slightly under it. 125 = 248,832 and 218 = 262,144. That is a pretty tight fit. The number of bits encoding this group is 18/5 = 3.6. In the 13 rank group it was 26/7 = 3.714 so as you can see, as the number of unfilled ranks decreases (ranks are filling up such as 5555, 3333), the number of bits to encode the cards decreases.

Here is my complete list of costs (# of bits per card) for all possible # of ranks to be seen:

13    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
  2      1/1=1.000=1
  1      0/1..4=0.0=0

So as you can clearly see, as the number of unfilled ranks decreases (which it will do every deck), the number of bits needed to encode each card also decreases. You might be wondering what happens if we fill a rank but we are not yet done a group. For example, if the first 7 cards in the deck were 5,6,7,7,7,7,K, what should we do? Easy, The K would normally drop the encoder from 13 rank encoding mode to 12 rank encoding mode. However, since we haven't yet filled the first block of 7 cards in 13 rank encoding mode, we include the K in that block to complete it. There is very little waste this way. There are also cases while we are trying to fill a block, the # of filled ranks bumps up by 2 or even more. That is also no problem as we just fill the block in the current encoding mode, then we pick up in the new encoding mode which may be 1,2,3... less or even stay in the same mode (as was the case in the first deck in the datafile as there are 3 full blocks in the 13 rank encoding mode). This is why it is important to make the blocksizes reasonable such as between size 1 and 7. If we made it size 20 for example, we would have to fill that block at a higher bitrate than if we let the encoder transition into a more efficient encoding mode (encoding less ranks).

When I ran this algorithm (by hand) on the first deck of cards in the data file (which was created using Fisher-Yates unbiased shuffle), I got an impressive 168 bits to encode which is almost identical to optimal binary encoding but requires no knowledge of ordinal positions of all possible decks, no very large numbers, and no binary searches. It does however require binary manipulations and also radix manipulations (powers of 13,12,11...).

Notice also that when the number of unfilled ranks = 1, the overhead is 0 bits per card. Best case (for encoding) is we want the deck to end on a run of the same cards (such as 7777) cuz those get encoded for "free" (no bits required for those). My encode program will suppress any output when the remaining cards are all the same rank. This is cuz the decoder will be counting cards for each deck and know if after seeing card 48, if some rank (like 7) has not yet been seen, all 4 remaining cards MUST be 7s. If the deck ends on a pair (such as 77), triple/set (such as 777) or a quad ( such as 7777), we get additional savings for that deck using my algorithm.

Another "pretty" thing about this algorithm is that it never needs to use any numbers larger than 32 bit so it wont cause problems in some languages that "don't like" large numbers. Actually the largest numbers need to be on the order of 226 which are used in the 13 rank encoding mode. From there they just get smaller. In fact, if I really wanted to, I could make the program so that it doesn't use anything larger than 16 bit numbers but this is not necessary as most computer languages can easily handle 32 bits well. Also this is beneficial to me since one of the bit functions I am using maxes out at 32 bit. It is a function to test if a bit is set or not.

In the first deck in the datafile, the encoding of cards is as follows (diagram to come later). Format is (groupsize, bits, rank encode mode):

(7,26,13) First 7 cards take 26 bits to encode in 13 rank mode.
(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)

This is a total of 52 cards and 168 bits for an average of about 3.23 bits per card. There is no ambiguity in either the encoder or the decoder. Both count cards and know which encode mode to use/expect.

Also notice that 18 cards, (more than 1/3rd of the deck), are encoded BELOW the 3.2 bits per card "limit". Unfortunately those are not enough cards to bring the overall average below about 3.2 bits per card. I imagine in the best case or near best case (where many ranks fill up early such as 54545454722772277...), the encoding for that particular deck might be under 3 bits per card, but of course it is the average case that counts. I think best case might be if all the quads are dealt in order which might never happen if given all the time in the universe and the fastest supercomputer. Something like 22223333444455556666777788889999TTTTJJJJQQQQKKKKAAAA. Here the rank encode mode would drop fast and the last 4 cards would have 0 bits of overhead. This special case takes only 135 bits to encode.

Also one possible optimization I am considering is to take all the ranks that have only 1 card remaining and treating those all as a special "rank" by placing them in a single "bucket". The reason here is if we do that, the encoder can drop into a more efficient packing mode quicker. For example, if we are in 10 rank encoding mode but we only have one more each of ranks 3,7, and K, those cards have much less chance of appearing than the other cards so it doesn't make much sense to treat them the same. If instead I dropped to 8 rank encoding mode which is more efficient that 10 rank mode, perhaps I could use fewer bits for that deck. When I see one of the cards in that special "grouped" bucket of several cards, I would just output that special "rank" (not a real rank but just an indicator we just saw something in that special bucket) and then a few more bits to tell the decoder which card in the bucket I saw, then I would remove that card from the group (since it just filled up). I will trace this by hand to see if any bit savings is possible using it. Note there should be no ambiguity using this special bucket because both the encoder and decoder will be counting cards and will know which ranks have only 1 card remaining. This is important because it makes the encoding process more efficient when the decoder can make correct assumptions without the encoder having to pass extra messages to it.

Here is the first full deck in the 3 million deck data file and a trace of my algorithm on it showing both the block groupings and the transitions to a lower rank encoding mode (like when transitioning from 13 to 12 unfilled ranks) as well as how many bits needed to encode each block. x and y are used for 11 and 10 respectively because unfortunately they happened on neighboring cards and don't display well juxtaposed.

         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

Note that there is some inefficiency when the encode mode wants to transition early in a block (when the block is not yet completed). We are "stuck" encoding that block at a slightly higher bit level. This is a tradeoff. Because of this and because I am not using every possible combination of the bit patterns for each block (except when it is an integer power of 2), this algorithm cannot be optimal but can approach 166 bits per deck. The average on my datafile is around 175. The particular deck was "well behaved" and only required 168 bits. Note that we only got a single 4 at the end of the deck but if instead we got all four 4s there, that is a better case and we would have needed only 161 bits to encode that deck, a case where the packing actually beats the entropy of a straight binary encode of the ordinal position of it.

I now have the code implemented to calculate the bit requirements and it is showing me on average, about 175 bits per deck with a low of 155 and a high of 183 for the 3 million deck test file. So my algorithm seems to use 9 extra bits per deck vs. the straight binary encode of the ordinal position method. Not too bad at only 5.5% additional storage space required. 176 bits is exactly 22 bytes so that is quite a bit better than 52 bytes per deck. Best case deck (didn't show up in 3 million deck test file) packs to 136 bits and worst case deck (did show up in testfile 8206 times), is 183 bits. Analysis shows worst case is when we don't get the first quad until close to (or at) card 40. Then as the encode mode wants to drop quickly, we are "stuck" filling blocks (as large as 7 cards) in a higher bit encoding mode. One might think that not getting any quads until card 40 would be quite rare using a well shuffled deck, but my program is telling me it happened 321 times in the testfile of 3 million decks so that it about 1 out of every 9346 decks. That is more often that I would have expected. I could check for this case and handle it with less bits but it is so rare that it wouldn't affect the average bits enough.

Also here is something else very interesting. If I sort the deck on the raw deck data, the length of prefixes that repeat a significant # of times is only about length 6 (such as 222244). However with the packed data, that length increases to about 16. That means if I sort the packed data, I should be able to get a significant savings by just indicating to the decoder a 16 bit prefix and then just output the remainder of the decks (minus the repeating prefix) that have that same prefix, then go onto the next prefix and repeat. Assuming I save even just 10 bits per deck this way, I should beat the 166 bits per deck. With the enumeration technique stated by others, I am not sure if the prefix would be as long as with my algorithm. Also the packing and unpacking speed using my algorithm is surprisingly good. I could make it even faster too by storing powers of 13,12,11... in an array and using those instead of expression like 13^5.

Regarding the 2nd level of compression where I sort the output bitstrings of my algorithm then use "difference" encoding: A very simple method would be to encode the 61,278 unique 16 bit prefixes that show up at least twice in the output data (and a maximum of 89 times reported) simply as a leading bit of 0 in the output to indicate to the 2nd level decompressor that we are encoding a prefix (such as 0000111100001111) and then any packed decks with that same prefix will follow with a 1 leading bit to indicate the non prefix part of the packed deck. The average # of packed decks with the same prefix is about 49 for each prefix, not including the few that are unique (only 1 deck has that particular prefix). It appears I can save about 15 bits per deck using this simple strategy (storing the common prefixes once). So assuming I really do get 15 bit saving per deck and I am already at about 175 bits per deck on the first level packing/compression, that should be a net of about 160 bits per deck, thus beating the 166 bits of the enumeration method.

After the 2nd level of compression using difference (prefix) encoding of the sorted bitstring output of the first encoder, I am now getting about 160 bits per deck. I use length 18 prefix and just store it intact. Since almost all (245013 out of 262144 = 93.5%) of those possible 18 bit prefixes show up, it would be even better to encode the prefixes. Perhaps I can use 2 bits to encode what type of data I have. 00 = regular length 18 prefix stored, 01= "1 up prefix" (same as previous prefix except 1 added), 11 = straight encoding from 1st level packing (approx 175 bits on average). 10=future expansion when I think of something else to encode that will save bits.

Did anyone else beat 160 bits per deck yet? I think I can get mine a little lower with some experimenting and using the 2 bit descriptors I mentioned above. Perhaps it will bottom out at 158ish. My goal is to get it to 156 bits (or better) because that would be 3 bits per card or less. Very impressive. Lots of experimenting to get it down to that level because if I change the first level encoding then I have to retest which is the best 2nd level encoding and there are many combinations to try. Some changes I make may be good for other similar random data but some may be biased towards this dataset. Not really sure but if I get the urge I can try another 3 million deck dataset to see what happens like if I get similar results on it.

One interesting thing (of many) about compression is you are never quite sure when you have hit the limit or are even approaching it. The entropy limit tells us how many bits we need if ALL possible occurrences of those bits occur about equally, but as we know, in reality, that rarely happens with a large number of bits and a (relatively) small # of trials (such as 3 million random decks vs almost 1050 bit combinations of 166 bits.

Does anyone have any ideas on how to make my algorithm better like what other cases I should encode that would reduce bits of storage for each deck on average? Anyone?

2 more things: 1) I am somewhat disappointed that more people didn't upvote my solution which although not optimal on space, is still decent and fairly easy to implement (I got mine working fine). 2) I did analysis on my 3 million deck datafile and noticed that the most frequently occurring card where the 1st rank fills (such as 4444) is at card 26. This happens about 6.711% of the time (for 201322 of the 3 million decks). I was hoping to use this info to compress more such as start out in 12 symbol encode mode since we know on average we wont see every rank until about middeck but this method failed to compress any as the overhead of it exceeded the savings. I am looking for some tweaks to my algorithm that can actually save bits.

So does anyone have any ideas what I should try next to save a few bits per deck using my algorithm? I am looking for a pattern that happens frequently enough so that I can reduce the bits per deck even after the extra overhead of telling the decoder what pattern to expect. I was thinking something with the expected probabilities of the remaining unseen cards and lumping all the single card remaining ones into a single bucket. This will allow me to drop into a lower encode mode quicker and maybe save some bits but I doubt it.

Also, F.Y.I., I generated 10 million random shuffles and stored them in a database for easy analysis. Only 488 of them end in a quad (such as 5555). If I pack just those using my algorithm, I get 165.71712 bits on average with a low of 157 bits and a high of 173 bits. Just slightly below the 166 bits using the other encoding method. I am somewhat surprised at how infrequent this case is (about 1 out of every 20,492 shuffles on average).


3
I notice that you've made about 24 edits in the space of 9 hours. I appreciate your desire to improve your answer. However, each time you edit the answer, it bumps this to the top of the front page. For that reason, we discourage excessive editing. If you expect to make many edits, would it be possible to batch up your edits, so you only make one edit every few hours? (Incidentally, note that putting "EDIT:" and "UPDATE:" in your answer is usually poor style. See meta.cs.stackexchange.com/q/657/755.)
D.W.

4
This is not the place to put progress reports, status updates, or blog items. We want fully-formed answers, not "coming soon" or "I have a solution but I'm not going to describe what it is".
D.W.

3
If someone is interested he will find the improved solution. The best way is to wait for full answer and post it then. If you have some updates a blog would do. I do not encourage this, but if you really must (I do not see valid reason why) you can write comment below your post and merge later. I also encourage you to delete all obsolete comments and incorporate them into one seamless question - it gets hard to read all. I try to make my own algorithm, different than any presented, but I am not happy with the results - so I do not post partials to be edited - the answer box is for full ones.
Evil

3
@DavidJames, I do understand. However, that still doesn't change our guidelines: please don't make so many edits. (If you'd like to propose improvements to the website, feel free to make a post on our Computer Science Meta or on meta.stackexchange.com suggesting it. Devs don't read this comment thread.) But in the meantime, we work with the software we have, and making many edits is discouraged because it bumps the question to the top. At this point, limiting yourself to one edit per day might be a good guideline to shoot for. Feel free to use offline editors or StackEdit if that helps!
D.W.

3
I'm not upvoting your answer for several reasons. 1) it is needless long and FAR too verbose. You can drastically reduce it's presentation. 2) there are better answers posted, which you choose to ignore for reasons unbeknownst to me. 3) asking about lack of upvotes is usually a "red flag" to me. 4) This has constantly remained in the front page due to an INSANE amount of edits.
Nicholas Mancuso
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.