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


31

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

13420
21304
32041
04213
40132

І як відомо багато гравців судоку, вам не потрібно всіх чисел для виведення решти цифр.

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

Різна інформація:

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

Тести можна знайти на Github . Ваш бал - це загальний розмір стислих тестових випадків.

EDIT: Станом на 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 . Це робить їх дуже легко стиснути набагато більше, ніж випадковий латинський квадрат. Хоча ваші правила говорять, що ми повинні мати однакові коефіцієнти стиснення для всіх латинських квадратів, це може бути легко розбити випадково. Тестові випадки повинні бути більш репрезентативними.
Anders Kaseorg

Відповіді:


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([…]), і я це зробив у поточній версії.
Anders Kaseorg

хммм. чи всі квадрати зберігаються в одному байтовому потоці? чи зберігається також інформація про їх розмір, або дешифратор припускає, що вони мають розмір 1,2,3,… тощо?
Сардж Борщ

@SargeBorsch Кожен квадрат стискається в окремий бітовий потік. Декомпресор однозначно відновлює розмір квадрата з потоку бітів, використовуючи описаний мною алгоритм. Жодне припущення не використовується.
Anders Kaseorg

7

МАТЛАБ , 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 Як ви розрізняєте слова різної довжини тоді? Наскільки я знаю, вам тоді потрібні додаткові префікси, чи не так?
недолік

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

О, так це має сенс, дякую!
недолік

3
З тієї ж причини, що ви робите це навпаки, це, мабуть, так, як ви звикли. =)
недолік

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 , тож цей символ кодується безкоштовно.
Денніс

Так, нові тестові справи зберегли 6 біт. :)
Денніс

3

J , 2444 байт

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

Стиснення, 36 байт

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

Вхід - це 2d масив, що представляє латинський квадрат. Кожен рядок перетворюється в індекс перестановки, і цей індекс перетворюється на список базових 255 цифр і замінюється значенням ASCII. Кожен рядок потім з'єднується за допомогою символу ASCII на рівні 255.

Декомпрес, 45 байт

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

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


2

Пітон, 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

Пітон 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 - 3572 3581 байт

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повертає генератор, який ліниво генерує всі перестановки, але якщо ви спробуєте перетворити його в list, він охоче генерує всі перестановки, що займає дуже довго.
Mego

Чи можете ви пояснити трохи краще, як використовувати свій код?
Mego

@ Mee Sure, можливо, я також застосую ледачу оцінку, хоча це все ще досить непомітно.
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();
        }
    }
}

Пермутор адаптований з класу, про який я писав кілька років тому для роботи з перестановками:

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.