Latin Quadrat Komprimierung


31

Ein lateinisches Quadrat ist ein Quadrat , das keine Symbole in den Zeilen oder Spalten wiederholt hat: .

13420
21304
32041
04213
40132

Und wie viele Sudoku-Spieler wissen, brauchen Sie nicht alle Zahlen, um die verbleibenden Zahlen abzuleiten.

Ihre Herausforderung besteht darin, ein lateinisches Quadrat in so wenige Bytes wie möglich zu komprimieren. Sie müssen ein oder zwei Programme bereitstellen, die komprimiert / dekomprimiert werden.

Verschiedene Infos:

  • Die verwendeten Zahlen sind immer 0..N-1, wo Nist die Länge der Kante des Quadrats, undN<=25
  • Bei der Dekomprimierung muss das lateinische Quadrat mit der Eingabe identisch sein.
  • Ihre Programme sollten in der Lage sein, alle lateinischen Quadrate (innerhalb der maximalen Quadratgröße) zu (de) komprimieren , nicht nur die, die ich bereitgestellt habe. Das Kompressionsverhältnis sollte ebenfalls ähnlich sein.
  • Sie müssen die Komprimierung und Dekomprimierung tatsächlich ausführen, um Ihre Punktzahl zu erhalten (keine Laufzeit am Ende des Universums).

Die Testfälle sind auf Github zu finden . Ihre Punktzahl ist die Gesamtgröße der komprimierten Testfälle.

BEARBEITEN: Ab 20:07 Uhr am 7. Juli habe ich die Testfälle aktualisiert (um ein Generationsproblem zu beheben). Bitte führen Sie Ihr Programm für die neuen Testfälle erneut aus. Danke Anders Kaseorg .


1
Nun, per definitionem jeder konnte Symbol verwendet werden, aber meine Testfälle nur passieren zu verwenden , 0obwohl n-1:)
Nathan Merrill


3
@ NathanMerrill naja, der Punkt durfte nur nverschiedene Symbole verwenden. : P
Martin Ender

1
@DavidC Es sollte keine Rolle spielen, da die Größe in Bytes gemessen wird .
Fehler

2
19 Ihrer 25 Testfälle (alle außer 4, 6, 8, 10, 12, 14) wurden durch Permutieren der Zeilen und Spalten des trivialen lateinischen Quadrats generiert, dessen ( i , j ) Eintrag i + j mod n ist . Dies macht es sehr einfach, sie viel mehr zu komprimieren als ein zufälliges lateinisches Quadrat. Obwohl Ihre Regeln besagen, dass wir für alle lateinischen Quadrate ähnliche Komprimierungsverhältnisse haben sollten, kann dies leicht versehentlich brechen. Die Testfälle sollten repräsentativer sein.
Anders Kaseorg

Antworten:


10

Python, 1281,375, 1268,625 Bytes

Wir codieren das lateinische Quadrat nacheinander als "Entscheidung", wobei jede Entscheidung eine der drei folgenden Formen hat:

  • welche Zahl in Zeile i , Spalte j steht ;
  • in Zeile i , in welche Spalte die Zahl k geht;
  • In Spalte j , in welcher Zeile die Zahl k steht .

Bei jedem Schritt treffen wir alle logischen Schlussfolgerungen, die wir aus früheren Entscheidungen ziehen können, und wählen dann die Entscheidung mit der geringstmöglichen Anzahl von Auswahlmöglichkeiten aus, die daher die geringste Anzahl von Bits zur Darstellung benötigen.

Die Auswahlmöglichkeiten werden von einem einfachen arithmetischen Decoder bereitgestellt (div / mod durch die Anzahl der Auswahlmöglichkeiten). Die Kodierung bleibt jedoch etwas redundant: Wenn k zu einem Quadrat dekodiert, bei dem das Produkt aller Auswahlen m war , dann dekodieren k + m , k + 2⋅ m , k + 3⋅ m ,… zu demselben Quadrat mit etwas Restzustand am Ende.

Wir nutzen diese Redundanz, um die Größe des Quadrats nicht explizit zu codieren. Der Dekomprimierer beginnt mit dem Versuch, ein Quadrat der Größe 1 zu dekodieren. Wenn der Dekodierer mit dem Reststatus fertig ist, wirft er dieses Ergebnis aus, subtrahiert m von der ursprünglichen Zahl, erhöht die Größe um 1 und versucht es erneut.

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

Ausgabe:

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

Ich versuche diesen Code bei Ideone, aber es gibt nur Laufzeitfehler. Ich änderte es mit stdin anstelle von Datei f. ideone.com/fKGSQd
edc65

@ edc65 Es funktioniert nicht, weil Ideones NumPy veraltet ist.
Dennis

@ edc65 Ideone hat NumPy 1.8.2, wofür es zu alt ist np.stack(). In diesem Fall kann es durch ersetzt werden np.array([…]), und das habe ich in der aktuellen Version getan.
Anders Kaseorg

Hmmm. Werden alle Quadrate in einem Byte-Stream gespeichert? Werden auch die Informationen zu ihrer Größe gespeichert, oder geht der Decoder davon aus, dass sie die Größe 1,2,3, ... usw. haben?
Sarge Borsch

@SargeBorsch Jedes Quadrat wird zu einem separaten Bitstrom komprimiert. Der Dekomprimierer stellt die Quadratgröße eindeutig aus dem Bitstrom unter Verwendung des von mir beschriebenen Algorithmus wieder her. Es wird keine Annahme verwendet.
Anders Kaseorg

7

MATLAB, 3'062.5 2'888.125 Bytes

Bei diesem Ansatz werden nur die letzte Zeile und die letzte Spalte des Quadrats getrennt und jeder Eintrag in Wörter mit einer bestimmten Bittiefe konvertiert. Die Bittiefe wird für das gegebene Größenquadrat minimal gewählt. (Vorschlag von @KarlNapf) Diese Wörter werden nur aneinander angehängt. Dekompression ist genau das Gegenteil.

Die Summe aller Testfälle beträgt 23'105 Bits oder 2'888.125 Bytes. (Gilt immer noch für die aktualisierten Testfälle, da die Größe meiner Ausgaben nur von der Größe der Eingabe abhängt.)

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

Sie könnten etwas mehr komprimieren, indem Sie eine variable Bitrate verwenden, wie zn=9..16 4 Bits ausreichen.
Karl Napf

@KarlNapf Wie unterscheidet man dann die unterschiedlich langen Wörter? Soweit ich weiß, benötigen Sie dann zusätzliche Präfixe, nicht wahr?
Fehler

Innerhalb einer Komprimierung nicht variabel, eher abhängig von der Größe des Quadrats. Wenn n> 16, verwenden Sie 5 Bit fest, wenn 8 <n <= 16, verwenden Sie 4 Bit fest und so weiter.
Karl Napf

Oh ja, das macht Sinn, danke!
Fehler

3
Aus dem gleichen Grund, aus dem Sie es umgekehrt machen, ist es wahrscheinlich so, wie Sie es gewohnt sind. =)
Fehler

7

Python 3, 10772 Bit (1346,5 Byte)

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

Das Komprimieren und Dekomprimieren der kombinierten Testfälle dauert 0,1 Sekunden.

Überprüfen Sie die Punktzahl auf Ideone .


Woah, willst du das erklären?
Nathan Merrill

1
Kurz gesagt, der Kompressor bewegt sich in Lesereihenfolge durch das Quadrat, verfolgt die Symbole, die bereits in dieser Zeile und Spalte vorkommen, und codiert den Index des Symbols in der aufsteigenden Liste der möglichen Symbole arithmetisch. Ich werde eine detaillierte Erklärung hinzufügen, nachdem ich meinen Code ein wenig aufgeräumt und getestet habe, ob die bijektive Basis 256 irgendwelche Bytes speichert.
Dennis

Ich bin nicht ganz sicher, was Ihr Code tut, aber ist es nicht möglich, die letzte Zeile wegzulassen und sie beim Dekomprimieren zu lösen?
Yytsi

@TuukkaX Wenn es nur ein mögliches Symbol len(possible)ist 1 und possible.index(rows[i][j])ist 0 , so dass Symbol ohne Kosten codiert wird.
Dennis

Ja, die neuen Testfälle haben 6 Bit gespart. :)
Dennis

3

J , 2444 Bytes

Verlässt sich beim A.Konvertieren in und aus einer Permutation von Ganzzahlen [0, n) und einem Permutationsindex auf das integrierte Element .

Komprimieren Sie 36 Byte

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

Die Eingabe ist ein 2D-Array, das das lateinische Quadrat darstellt. Jede Zeile wird in einen Permutationsindex konvertiert, und dieser Index wird in eine Liste mit Basis-255-Stellen konvertiert und durch einen ASCII-Wert ersetzt. Jede Zeichenfolge wird dann mit dem ASCII-Zeichen bei 255 verbunden.

Dekomprimieren Sie 45 Byte

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

Teilt die Eingabezeichenfolge bei jedem ASCII-Wert von 255 auf und parst jede Gruppe als 255-stellige Basis. Erstellen Sie dann unter Verwendung der Anzahl der Gruppen eine Liste von Ganzzahlen [0, Länge] und permutieren Sie sie entsprechend jedem Index und geben Sie sie als 2d-Array zurück.


2

Python, 6052 4521 3556 Bytes

compressNimmt das Quadrat wie in den Beispielen als mehrzeilige Zeichenfolge und gibt eine binäre Zeichenfolge zurück, wohingegen decompressdas Gegenteil der Fall ist.

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

Entfernen Sie die letzte Reihe + Spalte und machen Sie den Rest Reißverschluss zu.

  • Edit1: naja base64scheint nicht nötig
  • Edit2: konvertiert nun die gehackte Tabelle in eine binäre Zeichenkette und komprimiert sie nur bei Bedarf

2

Python 3, 1955 Bytes

Noch eine, die Permutationsindizes verwendet ...

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

Ausgabe

 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 Nimmt eine Liste von Integer-Tupeln und gibt einen String zurück.

dateDeCompress Nimmt einen String und gibt eine Liste von Integer-Tupeln zurück.

Kurz gesagt, für jede Zeile nimmt dieses Programm den Permutationsindex dieser Zeile und speichert ihn in Basis 36. Das Dekomprimieren dauert bei großen Eingaben sehr lange, aber die Komprimierung ist selbst bei großen Eingaben sehr schnell.

Verwendung:

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

Ergebnis: c|4|3|0

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

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


2
Sie würden wahrscheinlich eine viel bessere Laufzeit erzielen, wenn Sie Ihre permutationsAnrufe nicht in listAnrufe permutationseinbinden würden - Sie erhalten einen Generator, der träge alle Permutationen generiert, aber wenn Sie versuchen, daraus eine zu machen list, werden eifrig alle Permutationen generiert, die benötigt werden eine sehr lange Zeit.
Mego

Könnten Sie etwas besser erklären, wie Sie Ihren Code verwenden?
Mego

@Mego Klar, vielleicht implementiere ich die Lazy Evaluation auch, obwohl sie noch ziemlich unberechenbar ist.
Yytsi



1

Java, 2310 Bytes

Wir konvertieren jede Zeile des Quadrats in eine Zahl, die angibt, für welche lexikographische Permutation faktoradische Zahlen verwendet werden, die auch als faktorielles Zahlensystem bezeichnet werden und zum Nummerieren von Permutationen nützlich sind.

Wir schreiben das Quadrat in eine Binärdatei, wobei das erste Byte die Größe des Quadrats hat, und dann hat jede Zeile ein Byte für die Anzahl der Bytes in der Binärdarstellung einer Java-BigInteger, gefolgt von den Bytes dieser BigInteger.

Um den Prozess umzukehren und das Quadrat zu dekomprimieren, lesen wir zuerst die Größe und dann jede BigInteger und verwenden diese Zahl, um jede Zeile des Quadrats zu generieren.

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 wurde aus einer Klasse, die ich vor einigen Jahren geschrieben habe, angepasst, um mit Permutationen zu arbeiten:

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

Verwendung:

Mit einem lateinischen Quadrat in latin.txtkomprimieren Sie es:

java Latin -c latin.txt latin.compressed

Und dekomprimiere es:

java Latin -d latin.compressed latin.decompressed
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.