拉丁方压缩


31

一个拉丁方是一个没有重复的行或列符号的方形:

13420
21304
32041
04213
40132

正如许多Sudoku玩家所知道的那样,您不需要所有数字即可推断出剩余数字。

您面临的挑战是将拉丁方压缩为尽可能少的字节。 您需要提供一个或两个压缩/解压缩程序。

各种信息:

  • 使用的数字将始终为0..N-1,其中N是正方形边缘的长度,以及N<=25
  • 减压时,拉丁方必须与输入相同。
  • 您的程序应该能够解压缩任何拉丁正方形(在最大正方形大小之内),而不仅仅是我提供的。压缩率也应相似。
  • 您实际上必须运行压缩和解压缩器才能获得分数(无宇宙结束时间)

测试用例可以在github上找到。 您的分数是压缩测试用例的总大小。

编辑:从7月7日20:07开始,我更新了测试用例(以解决生成问题)。请在新的测试用例上重新运行您的程序。 谢谢Anders Kaseorg


1
好吧,根据定义,任何符号都可以使用,但是我的测试用例虽然碰巧就使用0n-1:)
Nathan Merrill


3
@NathanMerrill很好,只允许使用n不同的符号。:P
Martin Ender

1
@DavidC没关系,因为大小以字节为单位
瑕疵的2016年

2
您的25个测试用例中的19个(除4,6、8、10、12、14以外的所有用例)是通过排列(ij)项为i + j mod n的平凡拉丁方格的行和列生成的。这使它们比随机的拉丁方更容易压缩。尽管您的规则说所有拉丁方的压缩率都应相似,但这可能很容易被偶然破坏。测试用例应更具代表性。
Anders Kaseorg '16

Answers:


10

Python,1281.375 1268.625字节

我们一次将一个拉丁方编码为一个“决策”,其中每个决策都是以下三种形式之一:

  • 在第i行,第j列哪个数字;
  • 在第i行中,数字k进入哪一列;
  • 在第j列中,数字k进入哪一行。

在每一步中,我们都会根据先前的决策做出所有逻辑推断,然后选择具有最小数量可能选择的决策,因此将使用最小数量的位来表示。

选项由简单的算术解码器提供(div / mod为选项数)。但还有一些冗余编码:如果ķ解码正方形其中的选项数的所有产品是,然后ķ + ķ +2⋅ ķ +3⋅ ,...解码相同的方最后还有一些剩余状态。

我们利用这种冗余来避免对正方形的大小进行显式编码。解压缩器首先尝试对大小为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 '16

@ edc65它不起作用,因为Ideone的NumPy已过时。
丹尼斯

@ edc65 Ideone的NumPy 1.8.2太旧了np.stack()。在这种情况下,可以将其替换为np.array([…]),而我在当前版本中已这样做。
Anders Kaseorg '16

嗯。所有正方形都存储在一个字节流中吗?是否还存储了有关其大小的信息,或者解码器假定它们的大小为1,2,3等?
Sarge Borsch,

@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位就足够了。
Karl Napf

@KarlNapf然后如何区分不同长度的单词?据我所知,您还需要其他前缀,不是吗?
瑕疵的

在一次压缩中不可变,更像取决于正方形的大小。如果n> 16,则使用固定的5位,如果8 <n <= 16,则使用固定的4位,依此类推。
Karl Napf

哦,对,这很有意义,谢谢!
瑕疵的

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 '16

@TuukkaX当只有一个可能的符号len(possible)1possible.index(rows[i][j])0时,该符号将被免费编码。
丹尼斯

是的,新的测试用例节省了6位。:)
丹尼斯

3

J,2444字节

依靠内建A.函数在整数[0,n)和置换索引之间进行转换。

压缩36字节

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

输入是一个二维数组,表示拉丁方。每行将转换为一个排列索引,该索引将转换为以255为基数的列表,并替换为ASCII值。然后,使用255的ASCII字符连接每个字符串。

解压缩45字节

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

以255的每个ASCII值分割输入字符串,并将每个组解析为以255为基数的数字。然后使用组数创建一个整数[0,长度)列表,并根据每个索引对其进行置换,并将其作为2d数组返回。


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 3,581字节

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

@Mego当然,也许我也将实施惰性评估,尽管它仍然相当无争议。
Yytsi'7



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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.