Compression du carré latin

31

Un carré latin est un carré qui n'a pas de symboles répétés dans les lignes ou colonnes: .

13420
21304
32041
04213
40132

Et comme de nombreux joueurs de Sudoku le savent, vous n'avez pas besoin de tous les nombres pour déduire les nombres restants.

Votre défi est de compresser un carré latin en aussi peu d'octets que possible. Vous devez fournir un ou deux programmes qui compressent / décompressent.

Infos diverses:

  • Les nombres utilisés seront toujours 0..N-1, où Nest la longueur du bord du carré, etN<=25
  • Lors de la décompression, le carré latin doit être identique à l'entrée.
  • Votre programme (s) devrait être en mesure de (dé) compresser n'importe quel carré latin (dans la taille maximale du carré), pas seulement ceux que j'ai fournis. Les taux de compression devraient également être similaires.
  • Vous devez réellement exécuter la compression et le décompresseur pour obtenir votre score (pas d'exécutions de fin d'univers)

Les cas de test peuvent être trouvés sur github . Votre score est la taille totale des cas de test compressés.

EDIT: À 20h07 le 7 juillet, j'ai mis à jour les cas de test (afin de résoudre un problème de génération). Veuillez réexécuter votre programme sur les nouveaux cas de test. Merci Anders Kaseorg .

Nathan Merrill
la source
1
Eh bien, par définition, n'importe quel symbole pourrait être utilisé, mais mes cas de test se trouvent juste à utiliser 0cependant n-1:)
Nathan Merrill
3
@NathanMerrill bien, le point n'était autorisé qu'à utiliser ndifférents symboles. : P
Martin Ender
1
@DavidC Cela ne devrait pas avoir d'importance, car la taille est mesurée en octets .
flawr
2
19 de vos 25 cas de test (tous ceux sauf 4, 6, 8, 10, 12, 14) ont été générés en permutant les lignes et les colonnes du carré latin trivial dont l' entrée ( i , j ) est i + j mod n . Cela les rend très faciles à compresser bien plus qu'un carré latin aléatoire. Bien que vos règles stipulent que nous devrions avoir des taux de compression similaires pour tous les carrés latins, cela pourrait être facile à briser par accident. Les cas de test devraient être plus représentatifs.
Anders Kaseorg

Réponses:

10

Python, 1281,375 1268,625 octets

Nous codons le carré latin une «décision» à la fois, où chaque décision prend l'une de ces trois formes:

  • quel numéro va dans la ligne i , colonne j ;
  • à la ligne i , dans quelle colonne le nombre k entre;
  • dans la colonne j , dans quelle ligne le nombre k entre.

À chaque étape, nous faisons toutes les inférences logiques que nous pouvons en fonction des décisions précédentes, puis sélectionnons la décision avec le plus petit nombre de choix possibles, qui prennent donc le plus petit nombre de bits à représenter.

Les choix sont fournis par un simple décodeur arithmétique (div / mod par le nombre de choix). Mais cela laisse une certaine redondance dans l'encodage: si k décode en un carré où le produit de tous les nombres de choix était m , alors k + m , k + 2⋅ m , k + 3⋅ m ,… décodent vers le même carré avec un état résiduel à la fin.

Nous profitons de cette redondance pour éviter d'encoder explicitement la taille du carré. Le décompresseur commence par essayer de décoder un carré de taille 1. À chaque fois que le décodeur termine avec l'état de restes, il jette ce résultat, soustrait m du nombre d'origine, augmente la taille de 1 et essaie à nouveau.

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))

Sortie:

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
Anders Kaseorg
la source
J'essaie ce code sur ideone, mais il donne juste des erreurs d'exécution. Je l'ai modifié en utilisant stdin au lieu du fichier f. ideone.com/fKGSQd
edc65
@ edc65 Cela ne fonctionne pas car le NumPy d'Ideone est obsolète.
Dennis
@ edc65 Ideone a NumPy 1.8.2 qui est trop vieux pour np.stack(). Dans ce cas, il peut être remplacé par np.array([…]), et je l'ai fait dans la version actuelle.
Anders Kaseorg
hmmm. tous les carrés sont-ils stockés dans un flux d'octets? les informations sur leur taille sont-elles également stockées, ou le décodeur suppose-t-il qu'elles sont de taille 1,2,3,… etc?
Sarge Borsch
@SargeBorsch Chaque carré est compressé en un train de bits séparé. Le décompresseur récupère sans équivoque la taille carrée du flux binaire, en utilisant l'algorithme que j'ai décrit. Aucune hypothèse n'est utilisée.
Anders Kaseorg
7

MATLAB, 3'062.5 2'888.125 octets

Cette approche abandonne simplement la dernière ligne et la dernière colonne du carré, et convertit chaque entrée en mots d'une certaine profondeur de bits. La profondeur de bits est choisie minimale pour le carré de taille donné. (Suggestion de @KarlNapf) Ces mots sont simplement ajoutés les uns aux autres. La décompression est juste l'inverse.

La somme de tous les cas de test est de 23'105 bits ou 2'888.125 octets. (Toujours valable pour les cas de test mis à jour, car la taille de mes sorties ne dépend que de la taille de l'entrée.)

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
flawr
la source
Vous pouvez compresser un peu plus en utilisant un débit binaire variable, comme pour n=9..164 bits suffisent.
Karl Napf
@KarlNapf Comment différenciez-vous alors les mots de longueur différente? Autant que je sache, vous avez alors besoin de préfixes supplémentaires, n'est-ce pas?
flawr
Pas variable à l'intérieur d'une compression, plutôt en fonction de la taille du carré. Si n> 16, utilisez 5 bits fixes, si 8 <n <= 16, utilisez 4 bits fixes et ainsi de suite.
Karl Napf
Oh, c'est logique, merci!
flawr
3
Pour la même raison que vous le faites dans l'autre sens, c'est probablement la façon dont vous êtes habitué. =)
flawr
7

Python 3, 10772 bits (1346,5 octets)

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

Prend 0,1 seconde pour compresser et décompresser les cas de test combinés.

Vérifiez le score sur Ideone .

Dennis
la source
Woah, tu veux expliquer?
Nathan Merrill
1
En un mot, le compresseur parcourt le carré dans l'ordre de lecture, en gardant une trace des symboles qui figuraient déjà dans cette ligne et cette colonne, et en codant arithmétiquement l'index du symbole dans la liste ascendante des symboles possibles. J'ajouterai une explication détaillée après avoir nettoyé un peu mon code et testé si la base bijective 256 enregistre les octets.
Dennis
Je ne suis pas complètement sûr de ce que fait votre code, mais n'est-il pas possible de laisser la dernière ligne et de la résoudre pendant la décompression?
Yytsi
@TuukkaX Lorsqu'il n'y a qu'un seul symbole possible len(possible)est 1 et possible.index(rows[i][j])est 0 , donc ce symbole est encodé sans frais.
Dennis
Oui, les nouveaux cas de test ont enregistré 6 bits. :)
Dennis
3

J , 2444 octets

S'appuie sur la fonction intégrée A.pour convertir vers et depuis une permutation d'entiers [0, n) et un indice de permutation.

Compresser, 36 octets

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

L'entrée est un tableau 2D représentant le carré latin. Chaque ligne est convertie en un index de permutation, et cet index est converti en une liste de 255 chiffres de base et remplacé par une valeur ASCII. Chaque chaîne est ensuite jointe à l'aide du caractère ASCII à 255.

Décompresser, 45 octets

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

Fractionne la chaîne d'entrée à chaque valeur ASCII de 255 et analyse chaque groupe en tant que chiffres de base 255. Ensuite, en utilisant le nombre de groupes, créez une liste d'entiers [0, longueur) et permutez-la en fonction de chaque index et retournez-la sous la forme d'un tableau 2D.

milles
la source
2

Python, 6052 4521 3556 octets

compressprend le carré comme une chaîne multiligne, tout comme les exemples et renvoie une chaîne binaire, tandis que decompressfait le contraire.

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

Retirez la dernière ligne + colonne et fermez le reste.

  • Edit1: bien base64ne semble pas nécessaire
  • Edit2: maintenant convertir la table hachée en une chaîne binaire et compresser uniquement si nécessaire
Karl Napf
la source
2

Python 3, 1955 octets

Encore un autre qui utilise des indices de permutation ...

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))

sortie

 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
PM 2Ring
la source
2

Python3 - 3 572 3 581 octets

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 prend une liste de tuples entiers et retourne une chaîne.

dateDeCompress prend une chaîne et retourne une liste de tuples entiers.

En bref, pour chaque ligne, ce programme prend cet indice de permutation de lignes et l'enregistre en base 36. La décompression prend beaucoup de temps avec de grandes entrées mais la compression est vraiment rapide même sur de grandes entrées.

Usage:

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

résultat: c|4|3|0

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

résultat: [(2, 0, 1), (1, 2, 0), (0, 1, 2)]

Yytsi
la source
2
Vous obtiendrez probablement un temps d'exécution bien meilleur si vous n'encapsulez pas vos permutationsappels dans les listappels - permutationsrenvoie un générateur, qui génère paresseusement toutes les permutations, mais si vous essayez d'en faire un list, il génère avec impatience toutes les permutations, ce qui prend un très long temps.
Mego
Pourriez-vous expliquer un peu mieux comment utiliser votre code?
Mego
@Mego Bien sûr, je vais peut-être aussi implémenter l'évaluation paresseuse, bien qu'elle soit encore assez peu calculable.
Yytsi
1

Java, 2310 octets

Nous convertissons chaque ligne du carré en un nombre représentant la permutation lexicographique qu'il utilise en utilisant des nombres factoriels, également connu sous le nom de système de nombres factoriels , qui est utile pour la numérotation des permutations.

Nous écrivons le carré dans un fichier binaire où le premier octet est la taille du carré, puis chaque ligne a un octet pour le nombre d'octets dans la représentation binaire d'un Java BigInteger, suivi des octets de ce BigInteger.

Pour inverser le processus et décompresser le carré, nous lisons la taille, puis chaque BigInteger, et utilisons ce nombre pour générer chaque ligne du carré.

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();
        }
    }
}

Permutor est adapté d'une classe que j'ai écrite il y a quelques années pour travailler avec des permutations:

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

Usage:

Avec un carré latin latin.txtdedans, compressez-le:

java Latin -c latin.txt latin.compressed

Et décompressez-le:

java Latin -d latin.compressed latin.decompressed
David Conrad
la source