Compressão quadrada latina


31

Um quadrado latino é uma praça que tem símbolos nas linhas ou colunas não repetiu: .

13420
21304
32041
04213
40132

E como muitos jogadores de Sudoku sabem, você não precisa de todos os números para deduzir os números restantes.

Seu desafio é compactar um quadrado latino no menor número possível de bytes. Você precisa fornecer um ou dois programas que comprima / descompacta.

Várias informações:

  • Os números usados ​​sempre serão 0..N-1, onde Né o comprimento da borda do quadrado eN<=25
  • Na descompressão, o quadrado latino deve ser idêntico à entrada.
  • Seu (s) programa (s) deve (ão) descomprimir qualquer quadrado latino (dentro do tamanho máximo quadrado), não apenas os que eu forneci. As taxas de compressão também devem ser semelhantes.
  • Você deve realmente executar a compactação e o descompactador para obter sua pontuação (sem tempos de execução no final do universo)

Os casos de teste podem ser encontrados no github . Sua pontuação é o tamanho total de casos de teste compactados.

EDIT: A partir das 20:07 de 7 de julho, atualizei os casos de teste (para corrigir um problema de geração). Execute novamente o seu programa nos novos casos de teste. Obrigado Anders Kaseorg .


1
Bem, por definição, qualquer símbolo pode ser usado, mas os meus casos de teste apenas acontecer de usar 0embora n-1:)
Nathan Merrill


3
@ NathanMerrill bem, a questão estava sendo permitida apenas para usar nsímbolos diferentes. : P
Martin Ender

1
@ DavidDC Não deve importar, pois o tamanho é medido em bytes .
flawr

2
19 de seus 25 casos de teste (todos exceto 4, 6, 8, 10, 12, 14) foram gerados permutando as linhas e colunas do quadrado latino trivial cuja entrada ( i , j ) é i + j mod n . Isso os torna muito fáceis de compactar muito mais do que um quadrado latino aleatório. Embora suas regras digam que devemos ter taxas de compactação semelhantes para todos os quadrados latinos, pode ser fácil quebrar acidentalmente. Os casos de teste devem ser mais representativos.
Anders Kaseorg

Respostas:


10

Python, 1281.375 1268.625 bytes

Codificamos o quadrado latino uma “decisão” de cada vez, em que cada decisão é de uma destas três formas:

  • qual número vai na linha i , coluna j ;
  • na linha i , em qual coluna o número k entra;
  • na coluna j , qual linha o número k entra.

Em cada etapa, fazemos todas as inferências lógicas que podemos com base em decisões anteriores e, em seguida, escolhemos a decisão com o menor número de opções possíveis, o que leva o menor número de bits a representar.

As opções são fornecidas por um decodificador aritmético simples (div / mod pelo número de opções). Mas isso deixa alguma redundância na codificação: se k decodifica a uma praça onde o produto de todos os números de escolhas era m , então k + m , k + 2⋅ m , k + 3⋅ m , ... decodificação para a mesma casa com algum estado restante no final.

Aproveitamos essa redundância para evitar codificar explicitamente o tamanho do quadrado. O descompressor começa tentando decodificar um quadrado de tamanho 1. Sempre que o decodificador termina com o estado restante, ele joga fora esse resultado, subtrai m do número original, aumenta o tamanho em 1 e tenta novamente.

import numpy as np

class Latin(object):
    def __init__(self, size):
        self.size = size
        self.possible = np.full((size, size, size), True, dtype=bool)
        self.count = np.full((3, size, size), size, dtype=int)
        self.chosen = np.full((3, size, size), -1, dtype=int)

    def decision(self):
        axis, u, v = np.unravel_index(np.where(self.chosen == -1, self.count, self.size).argmin(), self.count.shape)
        if self.chosen[axis, u, v] == -1:
            ws, = np.rollaxis(self.possible, axis)[:, u, v].nonzero()
            return axis, u, v, list(ws)
        else:
            return None, None, None, None

    def choose(self, axis, u, v, w):
        t = [u, v]
        t[axis:axis] = [w]
        i, j, k = t
        assert self.possible[i, j, k]
        assert self.chosen[0, j, k] == self.chosen[1, i, k] == self.chosen[2, i, j] == -1

        self.count[1, :, k] -= self.possible[:, j, k]
        self.count[2, :, j] -= self.possible[:, j, k]
        self.count[0, :, k] -= self.possible[i, :, k]
        self.count[2, i, :] -= self.possible[i, :, k]
        self.count[0, j, :] -= self.possible[i, j, :]
        self.count[1, i, :] -= self.possible[i, j, :]
        self.count[0, j, k] = self.count[1, i, k] = self.count[2, i, j] = 1
        self.possible[i, j, :] = self.possible[i, :, k] = self.possible[:, j, k] = False
        self.possible[i, j, k] = True
        self.chosen[0, j, k] = i
        self.chosen[1, i, k] = j
        self.chosen[2, i, j] = k

def encode_sized(size, square):
    square = np.array(square, dtype=int)
    latin = Latin(size)
    chosen = np.array([np.argmax(square[:, :, np.newaxis] == np.arange(size)[np.newaxis, np.newaxis, :], axis=axis) for axis in range(3)])
    num, denom = 0, 1
    while True:
        axis, u, v, ws = latin.decision()
        if axis is None:
            break
        w = chosen[axis, u, v]
        num += ws.index(w)*denom
        denom *= len(ws)
        latin.choose(axis, u, v, w)
    return num

def decode_sized(size, num):
    latin = Latin(size)
    denom = 1
    while True:
        axis, u, v, ws = latin.decision()
        if axis is None:
            break
        if not ws:
            return None, 0
        latin.choose(axis, u, v, ws[num % len(ws)])
        num //= len(ws)
        denom *= len(ws)
    return latin.chosen[2].tolist(), denom

def compress(square):
    size = len(square)
    assert size > 0
    num = encode_sized(size, square)
    while size > 1:
        size -= 1
        square, denom = decode_sized(size, num)
        num += denom
    return '{:b}'.format(num + 1)[1:]

def decompress(bits):
    num = int('1' + bits, 2) - 1
    size = 1
    while True:
        square, denom = decode_sized(size, num)
        num -= denom
        if num < 0:
            return square
        size += 1

total = 0
with open('latin_squares.txt') as f:
    while True:
        square = [list(map(int, l.split(','))) for l in iter(lambda: next(f), '\n')]
        if not square:
            break

        bits = compress(square)
        assert set(bits) <= {'0', '1'}
        assert square == decompress(bits)
        print('Square {}: {} bits'.format(len(square), len(bits)))
        total += len(bits)

print('Total: {} bits = {} bytes'.format(total, total/8.0))

Saída:

Square 1: 0 bits
Square 2: 1 bits
Square 3: 3 bits
Square 4: 8 bits
Square 5: 12 bits
Square 6: 29 bits
Square 7: 43 bits
Square 8: 66 bits
Square 9: 94 bits
Square 10: 122 bits
Square 11: 153 bits
Square 12: 198 bits
Square 13: 250 bits
Square 14: 305 bits
Square 15: 363 bits
Square 16: 436 bits
Square 17: 506 bits
Square 18: 584 bits
Square 19: 674 bits
Square 20: 763 bits
Square 21: 877 bits
Square 22: 978 bits
Square 23: 1097 bits
Square 24: 1230 bits
Square 25: 1357 bits
Total: 10149 bits = 1268.625 bytes

Estou tentando esse código no ideone, mas ele apenas fornece erros de tempo de execução. Eu o modifiquei usando stdin em vez do arquivo f. ideone.com/fKGSQd
edc65 13/07/2016

@ edc65 Não funciona porque o NumPy da Ideone está desatualizado.
Dennis

@ edc65 A Ideone possui o NumPy 1.8.2, que é muito antigo para np.stack(). Nesse caso, ele pode ser substituído por np.array([…]), e eu o fiz na versão atual.
Anders Kaseorg

hmmm. todos os quadrados são armazenados em um fluxo de bytes? as informações sobre seu tamanho também são armazenadas ou o decodificador assume que elas são do tamanho 1,2,3,… etc?
Sarge Borsch

@SargeBorsch Cada quadrado é compactado em um fluxo de bits separado. O descompressor recupera o tamanho do quadrado inequivocamente do fluxo de bits, usando o algoritmo que descrevi. Nenhuma suposição é usada.
Anders Kaseorg 27/08/16

7

MATLAB, 3'062.5 2'888.125 bytes

Essa abordagem apenas abandona a última linha e a última coluna do quadrado e converte cada entrada em palavras com uma certa profundidade de bits. A profundidade do bit é escolhida mínima para o quadrado de tamanho especificado. (Sugestão de @KarlNapf) Essas palavras são apenas anexadas uma à outra. Descompressão é justamente o contrário.

A soma de todos os casos de teste é 23'105 bits ou 2'888.125 bytes. (Ainda é válido para os casos de teste atualizados, pois o tamanho das minhas saídas depende apenas do tamanho da entrada.)

function bin=compress(a)
%get rid of last row and column:
s=a(1:end-1,1:end-1);
s = s(:)';
bin = [];
%choose bit depth:
bitDepth = ceil(log2(numel(a(:,1))));
for x=s;
    bin = [bin, dec2bin(x,bitDepth)];
end
end

function a=decompress(bin)
%determine bit depth
N=0;
f=@(n)ceil(log2(n)).*(n-1).^2;
while f(N)~= numel(bin)
    N=N+1; 
end
bitDepth = ceil(log2(N));
%binary to decimal:
assert(mod(numel(bin),bitDepth)==0,'invalid input length')
a=[];
for k=1:numel(bin)/bitDepth;
    number = bin2dec([bin(bitDepth*(k-1) + (1:bitDepth)),' ']);
    a = [a,number];    
end
n = sqrt(numel(a));
a = reshape(a,n,n);
disp(a)
%reconstruct last row/column:
n=size(a,1)+1;
a(n,n)=0;%resize
%complete rows:
v = 0:n-1;
for k=1:n
    a(k,n) = setdiff(v,a(k,1:n-1));
    a(n,k) = setdiff(v,a(1:n-1,k));
end
end

Você pode comprimir um pouco mais usando uma taxa de bits variável, como por n=9..164 bits é suficiente.
1913 Karl Napf

@KarlNapf Como você discrimina as diferentes palavras? Até onde eu sei, você precisa de prefixos adicionais, não é?
flawr

Não é variável dentro de uma compressão, mais como dependendo do tamanho do quadrado. Se n> 16, use 5 bits fixos, se 8 <n <= 16 use 4 bits fixos e assim por diante.
Karl Napf

Oh certo, isso faz sentido, obrigado!
flawr

3
Pela mesma razão que você está fazendo o contrário, é provavelmente do jeito que você está acostumado. =)
flawr

7

Python 3, 10772 bits (1346,5 bytes)

def compress(rows):
    columns = list(zip(*rows))
    size = len(rows)
    symbols = range(size)
    output = size - 1
    weight = 25
    for i in symbols:
        for j in symbols:
            choices = set(rows[i][j:]) & set(columns[j][i:])
            output += weight * sorted(choices).index(rows[i][j])
            weight *= len(choices)
    return bin(output + 1)[3:]

def decompress(bitstring):
    number = int('1' + bitstring, 2) - 1
    number, size = divmod(number, 25)
    size += 1
    symbols = range(size)
    rows = [[None] * size for _ in symbols]
    columns = [list(column) for column in zip(*rows)]
    for i in symbols:
        for j in symbols:
            choices = set(symbols) - set(rows[i]) - set(columns[j])
            number, index = divmod(number, len(choices))
            rows[i][j] = columns[j][i] = sorted(choices)[index]
    return rows

Leva 0,1 segundos para compactar e descomprimir os casos de teste combinados.

Verifique a pontuação no Ideone .


Woah, gostaria de explicar?
Nathan Merrill

1
Em poucas palavras, o compressor percorre o quadrado em ordem de leitura, acompanhando os símbolos que já apareceram nessa linha e coluna e codificando aritmeticamente o índice do símbolo na lista crescente de possíveis símbolos. Vou adicionar uma explicação detalhada depois de limpar um pouco o meu código e testar se a base bijetiva 256 salva quaisquer bytes.
Dennis

Não tenho certeza do que seu código está fazendo, mas não é possível deixar a última linha de fora e resolvê-la enquanto descompacta?
Yytsi 12/07/16

@TuukkaX Quando existe apenas um símbolo possível, len(possible)é 1 e possible.index(rows[i][j])é 0 , para que o símbolo seja codificado sem nenhum custo.
Dennis

Os novos casos de teste economizaram 6 bits. :)
Dennis

3

J , 2444 bytes

Confia no builtin A. para converter de e para uma permutação de números inteiros [0, n) e um índice de permutação.

Compactar, 36 bytes

({:a.)joinstring<@(a.{~255&#.inv)@A.

A entrada é uma matriz 2D que representa o quadrado latino. Cada linha é convertida em um índice de permutação e esse índice é convertido em uma lista de dígitos base 255 e substituído por um valor ASCII. Cada sequência é unida usando o caractere ASCII em 255.

Descomprimir, 45 bytes

[:(A.i.@#)[:(_&,(255&#.&x:);._1~1,255&=)u:inv

Divide a sequência de entrada em cada valor ASCII de 255 e analisa cada grupo como 255 dígitos base. Em seguida, usando o número de grupos, crie uma lista de números inteiros [0, comprimento) e permute-a de acordo com cada índice e retorne-a como uma matriz 2D.


2

Python, 6052 4521 3556 bytes

compresspega o quadrado como uma cadeia de linhas múltiplas, assim como nos exemplos e retorna uma cadeia de caracteres binária, enquanto decompressfaz o oposto.

import bz2
import math

def compress(L):
 if L=="0": 
  C = []
 else:
  #split elements
  elems=[l.split(',') for l in L.split('\n')]
  n=len(elems)
  #remove last row and col
  cropd=[e[:-1] for e in elems][:-1]
  C = [int(c) for d in cropd for c in d]

 #turn to string
 B=map(chr,C)
 B=''.join(B)

 #compress if needed
 if len(B) > 36:
  BZ=bz2.BZ2Compressor(9)
  BZ.compress(B)
  B=BZ.flush()

 return B

def decompress(C):

 #decompress if needed
 if len(C) > 40:
  BZ=bz2.BZ2Decompressor()
  C=BZ.decompress(C)

 #return to int and determine length
 C = map(ord,C)
 n = int(math.sqrt(len(C)))
 if n==0: return "0"

 #reshape to list of lists
 elems = [C[i:i+n] for i in xrange(0, len(C), n)]

 #determine target length
 n = len(elems[0])+1
 L = []
 #restore last column
 for i in xrange(n-1):
  S = {j for j in range(n)}
  L.append([])
  for e in elems[i]:
   L[i].append(e)
   S.remove(e)
  L[i].append(S.pop())
 #restore last row
 L.append([])
 for col in xrange(n):
  S = {j for j in range(n)}
  for row in xrange(n-1):
   S.remove(L[row][col])
  L[-1].append(S.pop())
 #merge elements
 orig='\n'.join([','.join([str(e) for e in l]) for l in L])
 return orig

Remova a última linha + coluna e feche o restante.

  • Edit1: bem base64, não parece necessário
  • Edit2: agora convertendo a tabela cortada em uma string binária e comprima apenas se necessário

2

Python 3, 1955 bytes

Ainda outro que usa índices de permutação ...

from math import factorial

test_data_name = 'latin_squares.txt'

def grid_reader(fname):
    ''' Read CSV number grids; grids are separated by empty lines '''
    grid = []
    with open(fname) as f:
        for line in f:
            line = line.strip()
            if line:
                grid.append([int(u) for u in line.split(',') if u])
            elif grid:
                yield grid
                grid = []
    if grid:
        yield grid

def show(grid):
    a = [','.join([str(u) for u in row]) for row in grid]
    print('\n'.join(a), end='\n\n')

def perm(seq, base, k):
    ''' Build kth ordered permutation of seq '''
    seq = seq[:]
    p = []
    for j in range(len(seq) - 1, 0, -1):
        q, k = divmod(k, base)
        p.append(seq.pop(q))
        base //= j
    p.append(seq[0])
    return p

def index(p):
    ''' Calculate index number of sequence p,
        which is a permutation of range(len(p))
    '''
    #Generate factorial base code
    fcode = [sum(u < v for u in p[i+1:]) for i, v in enumerate(p[:-1])]

    #Convert factorial base code to integer
    k, base = 0, 1
    for j, v in enumerate(reversed(fcode), 2):
        k += v * base
        base *= j
    return k

def encode_latin(grid):
    num = len(grid)
    fbase = factorial(num)

    #Encode grid rows by their permutation index,
    #in reverse order, starting from the 2nd-last row
    codenum = 0
    for row in grid[-2::-1]:
        codenum = codenum * fbase + index(row)
    return codenum

def decode_latin(num, codenum):
    seq = list(range(num))
    sbase = factorial(num - 1)
    fbase = sbase * num

    #Extract rows
    grid = []
    for i in range(num - 1):
        codenum, k = divmod(codenum, fbase)
        grid.append(perm(seq, sbase, k))

    #Build the last row from the missing element of each column
    allnums = set(seq)
    grid.append([allnums.difference(t).pop() for t in zip(*grid)])
    return grid

byteorder = 'little'

def compress(grid):
    num = len(grid)
    codenum = encode_latin(grid)
    length = -(-codenum.bit_length() // 8)
    numbytes = num.to_bytes(1, byteorder)
    codebytes = codenum.to_bytes(length, byteorder)
    return numbytes + codebytes

def decompress(codebytes):
    numbytes, codebytes= codebytes[:1], codebytes[1:]
    num = int.from_bytes(numbytes, byteorder)
    if num == 1:
        return [[0]]
    else:
        codenum = int.from_bytes(codebytes, byteorder)
        return decode_latin(num, codenum)

total = 0
for i, grid in enumerate(grid_reader(test_data_name), 1):
    #show(grid)
    codebytes = compress(grid)
    length = len(codebytes)
    total += length
    newgrid = decompress(codebytes)
    ok = newgrid == grid
    print('{:>2}: Length = {:>3}, {}'.format(i, length, ok))
    #print('Code:', codebytes)
    #show(newgrid)

print('Total bytes: {}'.format(total))

saída

 1: Length =   1, True
 2: Length =   1, True
 3: Length =   2, True
 4: Length =   3, True
 5: Length =   5, True
 6: Length =   7, True
 7: Length =  11, True
 8: Length =  14, True
 9: Length =  20, True
10: Length =  26, True
11: Length =  33, True
12: Length =  41, True
13: Length =  50, True
14: Length =  61, True
15: Length =  72, True
16: Length =  84, True
17: Length =  98, True
18: Length = 113, True
19: Length = 129, True
20: Length = 147, True
21: Length = 165, True
22: Length = 185, True
23: Length = 206, True
24: Length = 229, True
25: Length = 252, True
Total bytes: 1955

2

Python3 - 3.572 3.581 bytes

from itertools import *
from math import *

def int2base(x,b,alphabet='0123456789abcdefghijklmnopqrstuvwxyz'):
    if isinstance(x,complex):
        return (int2base(x.real,b,alphabet) , int2base(x.imag,b,alphabet))
    if x<=0:
        if x==0:return alphabet[0]
        else:return  '-' + int2base(-x,b,alphabet)
    rets=''
    while x>0:
        x,idx = divmod(x,b)
        rets = alphabet[idx] + rets
    return rets

def lexicographic_index(p):
    result = 0
    for j in range(len(p)):
        k = sum(1 for i in p[j + 1:] if i < p[j])
        result += k * factorial(len(p) - j - 1)
    return result

def getPermutationByindex(sequence, index):
    S = list(sequence)
    permutation = []
    while S != []:
        f = factorial(len(S) - 1)
        i = int(floor(index / f))
        x = S[i]
        index %= f
        permutation.append(x)
        del S[i]
    return tuple(permutation)

alphabet = "abcdefghijklmnopqrstuvwxyz"

def dataCompress(lst):
    n = len(lst[0])

    output = alphabet[n-1]+"|"

    for line in lst:
        output += "%s|" % int2base(lexicographic_index(line), 36)

    return output[:len(output) - 1]

def dataDeCompress(data):
    indexes = data.split("|")
    n = alphabet.index(indexes[0]) + 1
    del indexes[0]

    lst = []

    for index in indexes:
        if index != '':
            lst.append(getPermutationByindex(range(n), int(index, 36)))

    return lst

dataCompress pega uma lista de tuplas inteiras e retorna uma string.

dateDeCompress pega uma string e retorna uma lista de tuplas inteiras.

Em resumo, para cada linha, esse programa pega o índice de permutação de linhas e o salva na base 36. A descompressão leva muito tempo com entradas grandes, mas a compactação é realmente rápida mesmo em entradas grandes.

Uso:

dataCompress([(2,0,1),(1,2,0),(0,1,2)])

resultado: c|4|3|0

dataDeCompress("c|4|3|0")

resultado: [(2, 0, 1), (1, 2, 0), (0, 1, 2)]


2
Você provavelmente obteria um tempo de execução muito melhor se não encerrasse suas permutationschamadas list- permutationsretorna um gerador, que gera preguiçosamente todas as permutações, mas se você tentar transformá-lo em um list, ele gera ansiosamente todas as permutações, o que leva muito tempo.
Mego

Você poderia explicar um pouco melhor como usar seu código?
Mego

@Mego Claro, talvez eu também implemente a avaliação preguiçosa, embora ainda seja bastante incontestável.
Yytsi



1

Java, 2310 bytes

Convertemos cada linha do quadrado em um número que representa qual permutação lexicográfica está usando números fatorádicos, também conhecido como sistema de números fatoriais. , que é útil para numerar permutações.

Escrevemos o quadrado em um arquivo binário em que o primeiro byte é o tamanho do quadrado e, em seguida, cada linha possui um byte para o número de bytes na representação binária de um Java BigInteger, seguido pelos bytes desse BigInteger.

Para reverter o processo e descomprimir o quadrado, lemos o tamanho de volta e, em seguida, cada BigInteger, e usamos esse número para gerar cada linha do quadrado.

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Latin {
    public static void main(String[] args) {
        if (args.length != 3) {
            System.out.println("java Latin {-c | -d} infile outfile");
        } else if (args[0].equals("-c")) {
            compress(args[1], args[2]);
        } else if (args[0].equals("-d")) {
            decompress(args[1], args[2]);
        } else {
            throw new IllegalArgumentException(
                "Invalid mode: " + args[0] + ", not -c or -d");
        }
    }

    public static void compress(String filename, String outname) {
        try (BufferedReader br = Files.newBufferedReader(Paths.get(filename))) {
            try (OutputStream os =
                    new BufferedOutputStream(new FileOutputStream(outname))) {
                String line = br.readLine();
                if (line == null) return;
                int size = line.split(",").length;
                if (size > 127) throw new ArithmeticException(
                    "Overflow: square too large");
                Permutor perm = new Permutor(size);
                os.write((byte) size); // write size of square

                do {
                    List<Integer> nums = Arrays.stream(line.split(","))
                        .map(Integer::new)
                        .collect(Collectors.toList());
                    byte[] bits = perm.which(nums).toByteArray();
                    os.write((byte) bits.length); // write length of bigint
                    os.write(bits); // write bits of bigint
                } while ((line = br.readLine()) != null);
            }
        } catch (IOException e) {
            System.out.println("Error compressing " + filename);
            e.printStackTrace();
        }
    }

    public static void decompress(String filename, String outname) {
        try (BufferedInputStream is =
                new BufferedInputStream(new FileInputStream(filename))) {
            try (BufferedWriter bw =
                    Files.newBufferedWriter(Paths.get(outname))) {
                int size = is.read(); // size of latin square
                Permutor perm = new Permutor(size);
                for (int i = 0; i < size; ++i) {
                    int num = is.read(); // number of bytes in bigint
                    if (num == -1) {
                        throw new IOException(
                            "Unexpected end of file reading " + filename);
                    }
                    byte[] buf = new byte[num];
                    int read = is.read(buf); // read bits of bigint into buf
                    if (read != num) {
                        throw new IOException(
                            "Unexpected end of file reading " + filename);
                    }
                    String row = perm.nth(new BigInteger(buf)).stream()
                        .map(Object::toString)
                        .collect(Collectors.joining(","));
                    bw.write(row);
                    bw.newLine();
                }
            }
        } catch (IOException e) {
            System.out.println("Error reading " + filename);
            e.printStackTrace();
        }
    }
}

O permutor é adaptado de uma classe que escrevi há alguns anos para trabalhar com permutações:

import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.math.BigInteger;
import static java.math.BigInteger.ZERO;
import static java.math.BigInteger.ONE;

public class Permutor {
    private final List<Integer> items;

    public Permutor(int n) {
        items = new ArrayList<>();
        for (int i = 0; i < n; ++i) items.add(i);
    }

    public BigInteger size() {
        return factorial(items.size());
    }

    private BigInteger factorial(int x) {
        BigInteger f = ONE;
        for (int i = 2; i <= x; ++i) {
            f = f.multiply(BigInteger.valueOf(i));
        }
        return f;
    }

    public List<Integer> nth(long n) {
        return nth(BigInteger.valueOf(n));
    }

    public List<Integer> nth(BigInteger n) {
        if (n.compareTo(size()) > 0) {
            throw new IllegalArgumentException("too high");
        }
        n = n.subtract(ONE);
        List<Integer> perm = new ArrayList<>(items);
        int offset = 0, size = perm.size() - 1;
        while (n.compareTo(ZERO) > 0) {
            BigInteger fact = factorial(size);
            BigInteger mult = n.divide(fact);
            n = n.subtract(mult.multiply(fact));
            int pos = mult.intValue();
            Integer t = perm.get(offset + pos);
            perm.remove((int) (offset + pos));
            perm.add(offset, t);
            --size;
            ++offset;
        }
        return perm;
    }

    public BigInteger which(List<Integer> perm) {
        BigInteger n = ONE;
        List<Integer> copy = new ArrayList<>(items);
        int size = copy.size() - 1;
        for (Integer t : perm) {
            int pos = copy.indexOf(t);
            if (pos < 0) throw new IllegalArgumentException("invalid");
            n = n.add(factorial(size).multiply(BigInteger.valueOf(pos)));
            copy.remove((int) pos);
            --size;
        }
        return n;
    }
}

Uso:

Com um quadrado latino latin.txt, comprima-o:

java Latin -c latin.txt latin.compressed

E descompacte-o:

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