Сжатие латинского квадрата


31

Латинский квадрат представляет собой квадрат , который не повторяется символов в строках или столбцах: .

13420
21304
32041
04213
40132

И, как знают многие игроки в судоку, вам не нужны все числа, чтобы вывести оставшиеся числа.

Ваша задача - сжать латинский квадрат до как можно меньшего числа байтов. Вы должны предоставить одну или две программы, которые сжимают / распаковывают.

Различная информация:

  • Используемые числа всегда будут 0..N-1, где Nдлина края квадрата, иN<=25
  • При декомпрессии латинский квадрат должен быть идентичен вводу.
  • Ваша программа (ы) должна быть способна (де) сжать любой латинский квадрат (в пределах максимального размера квадрата), а не только те, которые я предоставил. Коэффициенты сжатия должны быть одинаковыми.
  • Вы должны фактически запустить сжатие и распаковщик, чтобы получить ваш счет (Нет времени выполнения конца вселенной)

Тестовые случаи можно найти на github . Ваша оценка - общий размер сжатых тестовых случаев.

РЕДАКТИРОВАТЬ: По состоянию на 20:07 7 июля я обновил контрольные примеры (чтобы исправить проблему поколения). Пожалуйста, перезапустите вашу программу на новых тестовых случаях. Спасибо, Андерс Касорг .


1
Ну, по определению, любой символ может быть использован, но мои тестовые случаи просто случаются использовать 0хотя n-1:)
Натан Меррилл


3
@NathanMerrill хорошо, дело только в том, чтобы использовать nразные символы. : P
Мартин Эндер

1
@DavidC Это не должно иметь значения, так как размер измеряется в байтах .
flawr

2
19 из 25 ваших тестовых случаев (все, кроме 4, 6, 8, 10, 12, 14) были сгенерированы путем перестановки строк и столбцов тривиального латинского квадрата, чья запись ( i , j ) равна i + j mod n . Это делает их очень легко сжать намного больше, чем случайный латинский квадрат. Хотя ваши правила гласят, что у нас должны быть одинаковые коэффициенты сжатия для всех латинских квадратов, это может быть легко нарушено случайно. Контрольные примеры должны быть более репрезентативными.
Андерс Касеорг

Ответы:


10

Питон, 1281,375 1268,625 байт

Мы кодируем латинский квадрат по одному «решению» за раз, где каждое решение имеет одну из этих трех форм:

  • какое число входит в строку i , столбец j ;
  • в строке i , в какой столбец входит число k ;
  • в столбце j , в какую строку входит число k .

На каждом шаге мы делаем все логические выводы, которые можем, основываясь на предыдущих решениях, а затем выбираем решение с наименьшим числом возможных вариантов выбора, которые, следовательно, принимают наименьшее количество битов для представления.

Выбор предоставляется простым арифметическим декодером (div / mod по количеству вариантов). Но это оставляет некоторую избыточность в кодировке: если k декодирует в квадрат, где произведение всех чисел выбора было m , то k + m , k + 2⋅ m , k + 3⋅ m ,… декодируют в тот же квадрат с некоторым оставшимся состоянием в конце.

Мы используем эту избыточность, чтобы избежать явного кодирования размера квадрата. Декомпрессор начинает с попытки декодирования квадрата размером 1. Всякий раз, когда декодер завершает работу с оставшимся состоянием, он выбрасывает этот результат, вычитает m из исходного числа, увеличивает размер на 1 и пытается снова.

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

Выход:

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

Я пытаюсь этот код в Ideone, но он просто дает ошибки во время выполнения. Я изменил его, используя stdin вместо файла f. ideone.com/fKGSQd
edc65

@ edc65 Не работает, потому что NumPy от Ideone устарел.
Деннис

@ edc65 Ideone имеет NumPy 1.8.2, который слишком стар для np.stack(). В этом случае его можно заменить на np.array([…]), и я сделал это в текущей версии.
Андерс Касеорг

хммм. все квадраты хранятся в одном байтовом потоке? также хранится информация об их размере или декодер предполагает, что они имеют размер 1,2,3 и т. д.?
Сардж Борщ

@SargeBorsch Каждый квадрат сжимается в отдельный поток битов. Декомпрессор однозначно восстанавливает размер квадрата из потока битов, используя алгоритм, который я описал. Предположение не используется.
Андерс Касеорг

7

MATLAB, 3'062,5 2'888,125 байт

Этот подход просто отбрасывает последнюю строку и последний столбец квадрата и преобразует каждую запись в слова определенной битовой глубины. Битовая глубина выбирается минимальной для заданного размера квадрата. (Предложение @KarlNapf) Эти слова просто добавляются друг к другу. Декомпрессия как раз наоборот.

Сумма для всех тестовых случаев составляет 23'105 бит или 2'888.125 байт. (Все еще сохраняется для обновленных тестовых случаев, поскольку размер моих выходных данных зависит только от размера входных данных.)

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

Вы можете сжать немного больше, используя переменную скорость передачи данных, как для n=9..164 битов достаточно.
Карл Напф

@KarlNapf Как вы различаете слова разной длины? Насколько я знаю, вам нужны дополнительные префиксы, не так ли?
flawr

Не изменяется внутри одного сжатия, больше похоже на размер квадрата. Если n> 16, тогда использовать фиксированные 5 битов, если 8 <n <= 16, использовать фиксированные 4 бита и так далее.
Карл Напф

Ах да, это имеет смысл, спасибо!
flawr

3
По той же причине, по которой вы делаете это наоборот, вероятно, вы привыкли. =)
flawr

7

Python 3, 10772 бита (1346,5 байт)

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

Требуется 0,1 секунды для сжатия и распаковки комбинированных тестовых случаев.

Проверьте счет на Ideone .


Вау, хочешь объяснить?
Натан Меррилл

1
В двух словах, компрессор перемещается по квадрату в порядке чтения, отслеживая символы, которые уже появились в этой строке и столбце, и арифметически кодирует индекс символа в возрастающем списке возможных символов. Я добавлю подробное объяснение после небольшой очистки кода и проверки, сохраняет ли биективная база 256 какие-либо байты.
Деннис

Я не совсем уверен, что делает ваш код, но разве нельзя оставить последнюю строку и решить ее при распаковке?
Yytsi

@TuukkaX Когда есть только один возможный символ len(possible)является 1 и possible.index(rows[i][j])является 0 , так что символ кодируется без каких - либо затрат.
Деннис

Yay, новые тестовые случаи сохранили 6 бит. :)
Денис

3

J 2444 байта

Полагается на встроенную функцию A.преобразования в и из перестановки целых чисел [0, n) и индекса перестановки.

Компресс, 36 байт

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

На входе находится двумерный массив, представляющий латинский квадрат. Каждая строка преобразуется в индекс перестановки, и этот индекс преобразуется в список из 255 базовых цифр и заменяется значением ASCII. Каждая строка затем соединяется с использованием символа ASCII на 255.

Распаковка, 45 байт

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

Разбивает входную строку при каждом значении ASCII 255 и анализирует каждую группу как базовые 255 цифр. Затем, используя количество групп, создайте список целых чисел [0, длина), переставьте его в соответствии с каждым индексом и верните в виде 2-мерного массива.


2

Python, 6052 4521 3556 байт

compressпринимает квадрат как многострочную строку, как в примерах, и возвращает двоичную строку, тогда как decompressделает наоборот.

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

Удалите последний ряд + столбец и застегните остальное.

  • Edit1: ну base64не кажется необходимым
  • Edit2: теперь преобразование нарезанной таблицы в двоичную строку и сжатие только при необходимости

2

Python 3, 1955 байт

Еще один, который использует индексы перестановки ...

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

выход

 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 351 байт

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 берет список целочисленных кортежей и возвращает строку.

dateDeCompress берет строку и возвращает список целочисленных кортежей.

Короче говоря, для каждой строки эта программа берет индекс перестановки строк и сохраняет его в базе 36. Распаковка занимает много времени с большими входами, но сжатие действительно быстрое даже на больших входах.

Использование:

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

результат: c|4|3|0

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

результат: [(2, 0, 1), (1, 2, 0), (0, 1, 2)]


2
Вы, вероятно, получили бы намного лучшее время выполнения, если бы не обернули свои permutationsвызовы в listвызовы - permutationsвозвращает генератор, который лениво генерирует все перестановки, но если вы пытаетесь превратить его в a list, он охотно генерирует все перестановки, что требует очень долгое время.
Мего

Не могли бы вы объяснить немного лучше, как использовать ваш код?
Мего

@Mego Конечно, возможно, я тоже реализую ленивую оценку, хотя она все еще довольно неисчислима.
Yytsi



1

Java, 2310 байт

Мы преобразуем каждую строку квадрата в число, представляющее, какую лексикографическую перестановку он использует, используя факторические числа, также известные как система факторных чисел. , которая полезна для нумерации.

Мы записываем квадрат в двоичный файл, где первый байт является размером квадрата, а затем у каждой строки есть один байт для числа байтов в двоичном представлении Java BigInteger, за которым следуют байты этого BigInteger.

Чтобы повернуть процесс вспять и распаковать квадрат, мы читаем размер обратно, а затем каждый BigInteger и используем это число для генерации каждой строки квадрата.

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 адаптирован из класса, который я написал несколько лет назад, для работы с перестановками:

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

Использование:

С латинским квадратом latin.txtсожмите это:

java Latin -c latin.txt latin.compressed

И распакуйте его:

java Latin -d latin.compressed latin.decompressed
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.