Compressão ASCII Maze

9

Desafio

Crie um algoritmo de compactação especializado para compactar labirintos ASCII. Você precisará criar um algoritmo de compactação e um algoritmo de descompactação. Sua pontuação será baseada no tamanho dos labirintos compactados.

Labirintos

Estes labirintos são feitas principalmente dos caracteres (pavimentos), +, -, |, e #(paredes), e exatamente uma cada de ^(iniciar) e $(final). Eles também podem conter letras ASCII, que contam como ladrilhos. Para os propósitos deste desafio, os labirintos não precisam ser solucionáveis ​​e o significado real do conteúdo do labirinto é irrelevante.

  • + será usado para células de parede onde houver pelo menos uma célula de parede adjacente horizontalmente e pelo menos uma célula de parede adjacente verticalmente.
  • | será usado para células da parede onde houver pelo menos uma célula da parede adjacente verticalmente, mas nenhuma célula da parede adjacente horizontalmente.
  • - será usado para células da parede onde houver pelo menos uma célula da parede adjacente horizontalmente, mas nenhuma célula da parede adjacente verticalmente
  • # será usado apenas para células da parede que não são ortogonalmente adjacentes a outras células da parede.

Todos os labirintos são retangulares, mas não necessariamente têm um alinhamento regular de grade / parede.

Labirintos para comprimir

Labirinto 1

+----+----
|  o |    |
| -- | o--+
|    | |  $
 --^-+-+---

Labirinto 2

+-----+---+
|  a  |   |
^ +-+-+ # |
| | |  B  |
| | | --+ |
|   c   | $
+-------+--

Labirinto 3

----------+-+-+-----+-+
^         | | |     | |
+-- --+R #  | |p| | | |
|     | |       | |   |
+---+ +-+-+-- +-+ | | |
|  m| | | |   |   | | |
| +-+ | | | | | --+ | |
| | |    h  | |   | | |
| | | | | |  #  --+-+ |
|     | | | | |  S|   $
+-----+-+-+-+-+---+----

Labirinto 4

+-----+---+-+---+-------^-----+
|     |x  | |   |     tsrq    |
+-+-- +-- | +--  #  --+---- --+
| |   |           |   |       |
| | | | | +-+-+---+ | +-- | +-+
| | | u | | | |     | |   | | |
| +-+ | | | | +---- +-+---+ | |
| |   | |   |    y  |       w |
| | --+ | --+ +-- | +---- | | |
|     | |   | |   | |     | | |
+-- --+ +-+ | | | | +-- | +-+-+
|     | | |   | | | |   |     |
$ | --+-+ | --+-+ | +-+-+-- --+
| |   |      z|   |   |    v  |
+-+---+-------+---+---+-------+

Labirinto 5

++ -----------+
++-       Beep|
$  ----+---+--+
+-+boop|   |  |
| +--- | | | ++
|      | |  +++
+------+-+--+ ^

Labirinto 6

+-$---------------+-+--
|                 | |j 
| |l ---- # ---+ |  |  
| | |       m  | +--+ |
| | | +-+---- #       |
| | | | |      +----+ |
|o| | | | +----+    | |
|       | |    | -- | |
| | | | | | -+ |    | |
| | | | |  | | +--- | |
| | | | +- | | |   | ++
+-+ |n| |  | ++ +--+ | 
    | |   -+- | |  | +-
+---+ +---    |  | |  ^
|    |     --+ --+ | | 
| -- | |  k  |     | ++
|    | |      +--- | ++
|    |      | |    |  |
+-- -+----  | +----+--+

Labirinto 7

+---+-+-------------+-+^+-----+-------+---+-+---+-+---+-+---+
|   |c|             | | |  c  |       |   | |   | |   |c|   |
+-- | | +-- +-- # | | | +-- --+ +---- +-- | +-+ | | +-+ | --+
|       |   |     | |           |         |   | |c| |       |
| | +-- | +-+-- +-+ +-- # +- # -+-- +-- | | --+ | | | | --+C|
|c| |   | | c   |         |         |c  |             |   | |
+-+-+---+-+-----+---------+---------+---+-------------+---+$|

Labirinto 8

------+-+-+---+-+---+-----------+---+-----+---------------+-+
^     | | |   | |   |           |   |     |      r        | |
+-- | | | t | | +-- +----- # ---+-- +-- --+-- ----+-+ --+ | |
|   |   | | |   |   |         r |   |             | |   |   |
| | | | | +-+ --+ --+-- --------+-- | ----+ --+ | | | --+ | |
| |r| |            rotation               |   | |   |   | | $
+-+-+-+-----------------------------------+---+-+---+---+-+--

Labirinto 9

|$|^--+-+---+-----+-+---+-+-+---+---+-+---+-----+
| |   | |   |     | |   | | | f |   | |   |     |
| +-+ | | # +-+ --+ +-+ | | | # | +-+ +-- | ----+
|   |       | |    f| |           | | |   |   f |
| |F+-+ | | | | +---+ | | | ----+-+ | | --+ --+-+
| |   | | |     |     | | |   f |   |         | |
| | | | +-+-+---+-- | | | +-+-+-+ +-+ +--- # -+ |
| | | |     |   |   |   | | | |   | | |         |
+-+-+ | +---+ --+ | +---+-+ | | --+ f | | | | --+
|     | |         |                 | | | | |   |
| --+f| | | +-- --+--f--+ --+ | ----+ | +-+ +---+
|   |     | |     |     |   | |           |     |
+---+-----+-+-----+-----+---+-+-----------+-----+

Labirinto 10

+-----+-+-----------+
|  q  | |         q |
|Q+-+ | +-+-+-+---- |
$ | |     | | |  q  |
+-+ | | | | | +-- +-+
| |   | |     |   | |
| +-- +-+ |q| +-+ | |
|    q|   | |   |   |
| | | +-- | +-+ | --+
| | | |   | | |     |
+-+-+-+ +-+-+ +-- | |
|       |         | |
+--- # -+ | | +-- | |
|  q      | | |   | ^
+-+ +-- | | +-+ | +-+
| | |   | |q|   |   |
| +-+-+ | +-+-- | | |
|     | | |     | | |
| | | +-+-+-- +-+ +-+
| | |         | q   |
+-+-+---------+-----+

Regras, Premissas, Pontuação

  • As brechas padrão são proibidas
    • Escreva um programa geral, não um que funcione apenas para os dez casos de teste. Ele deve ser capaz de lidar com qualquer labirinto arbitrário.
  • Você pode assumir que haverá exatamente uma entrada e uma saída. As entradas e saídas sempre estarão na fronteira do labirinto.
  • Você pode assumir que todas as entradas usam paredes que seguem as regras enumeradas acima. Seu algoritmo de compactação não precisa funcionar para labirintos contendo paredes que violam essas regras.
  • Labirintos de entrada podem ou não ser solucionáveis.
  • Você pode assumir que o labirinto não terá mais que 100 caracteres em qualquer direção.
  • Você pode assumir que as letras não aparecerão na borda do labirinto. (já que este é o caso dos exemplos fornecidos)
  • Sua pontuação é o tamanho total, em bytes (octetos), de todos os labirintos compactados.
    • Você pode usar hex, base64, seqüências binárias ou qualquer formato semelhante como representação para o seu labirinto compactado, se achar mais conveniente. Você ainda deve contar o resultado em octetos inteiros, arredondados para cada labirinto (por exemplo, 4 dígitos base64 são 3 bytes, 2 dígitos hexadecimais são 1 byte, 8 dígitos binários são 1 byte, etc ...)
    • Menor pontuação ganha!
Beefster
fonte
Existe um limite de tamanho para um labirinto?
Modalidade de ignorância
@EmbodimentofIgnorance 100x100
Beefster 15/03
@ Arnauld, na verdade, era um problema de copiar e colar, mas acho que a formatação SE retira os espaços no final da linha de qualquer maneira. Sim, deveria ser preenchido com espaço.
Beefster 16/03/19
@ChasBrown, que conta como uma brecha padrão, o que significa que é banido por padrão.
Beefster 16/03/19
11
@schnaader, isso parece razoável, dado o exemplo de casos de teste.
Beefster 26/03/19

Respostas:

5

JavaScript (Node.js) , pontuação =  586 541 503 492  479 bytes

As paredes são armazenadas como um fluxo de bits codificado por Huffman, descrevendo se uma função de previsão está retornando a estimativa correta ou não.

(d,c)dc

Experimente online!

Comum

const HUFFMAN = [
  '00',       // 0000
  '010',      // 0001
  '1001',     // 0010
  '11100',    // 0011
  '011',      // 0100
  '101',      // 0101
  '11110',    // 0110
  '100010',   // 0111
  '110',      // 1000
  '11101',    // 1001
  '1111100',  // 1010
  '1111101',  // 1011
  '10000',    // 1100
  '1111110',  // 1101
  '100011',   // 1110
  '1111111'   // 1111
];

let bin = (n, w) => n.toString(2).padStart(w, '0');

let wallShape = (row, x, y) => {
  let vWall = (row[y - 1] || [])[x] | (row[y + 1] || [])[x],
      hWall = row[y][x - 1] | row[y][x + 1];

  return ' -|+'[row[y][x] ? vWall * 2 | hWall : 0];
}

let predictWall = (row, x, y, w, h) => {
  let prvRow = row[y - 1] || [];
  return !x | !y | x == w - 1 | y == h - 1 | (prvRow[x] | row[y][x - 1]) & !prvRow[x - 1];
}

Compressão

let pack = str => {
  let row = str.split('\n').map(r => [...r]),
      w = row[0].length,
      h = row.length;

  let wall = row.map((r, y) => r.map((c, x) => +/[-+|]/.test(c)));

  if(row.some((r, y) => r.some((c, x) => wall[y][x] && wallShape(wall, x, y) != c))) {
    throw "invalid maze";
  }

  row = wall.map((r, y) => r.map((v, x) => predictWall(wall, x, y, w, h) ^ v));
  row = row.map(r => r.join('')).join('');
  row = row.replace(/.{1,4}/g, s => HUFFMAN[parseInt(s.padEnd(4, '0'), 2)]);

  str =
    str.replace(/[\n|+-]/g, '').replace(/ *(\S)/g, (s, c) => {
      let n = c.charCodeAt(),
          i = '^$#'.indexOf(c);

      return (
        bin(s.length > 63 ? 0xFC000 | s.length - 1 : s.length - 1, 6) +
        bin(~i ? i : n < 91 ? (n > 80 ? 0x1F0 : 0x1E0) | ~-n & 15 : n - 94, 5)
      );
    }).trim();

  return (
    Buffer.from(
      (bin(w, 7) + bin(h, 7) + row + str)
      .match(/.{1,8}/g).map(s => parseInt(s.padEnd(8, '0'), 2))
    ).toString('binary')
  );
}

Descompressão

let unpack = str => {
  str = [...str].map(c => bin(c.charCodeAt(), 8)).join('');

  let x, y, n, i, s,
      ptr = 0,
      read = n => parseInt(str.slice(ptr, ptr += n), 2),
      w = read(7),
      h = read(7),
      row = [];

  for(x = s = ''; s.length < w * h;) {
    ~(i = HUFFMAN.indexOf(x += read(1))) && (s += bin(i, 4), x = '');
  }
  for(i = y = 0; y < h; y++) {
    for(row[y] = [], x = 0; x < w; x++) {
      row[y][x] = predictWall(row, x, y, w, h) ^ s[i++];
    }
  }

  row = row.map((r, y) => r.map((c, x) => wallShape(row, x, y)));

  for(i = 0; str[ptr + 10];) {
    for(
      n = (n = read(6)) == 0x3F ? read(14) + 1 : n + 1;
      n -= row[i / w | 0][i % w] == ' ';
      i++
    ) {}

    row[i / w | 0][i % w] = String.fromCharCode(
      (n = read(5)) >= 0x1E ? read(4) + (n == 0x1F ? 81 : 65) : [94, 36, 35][n] || n + 94
    );
  }
  return row.map(r => r.join('')).join('\n');
}

Como?

Um labirinto é codificado como um fluxo de bits que é eventualmente convertido em uma string.

Cabeçalho

O cabeçalho consiste em:

  • W
  • h

Dados da parede

0 01 1

0 01 1

  • 000000
  • 0100001
  • 10010010
  • 111000011
  • 0110100
  • etc.

WnPnCn

Wn=PnCn

As formas finais das paredes são deduzidas de maneira semelhante à resposta de Nick Kennedy .

Caracteres especiais

Cada caractere especial é codificado como:

  • 1 1

    • 63.
    • 111111
  • O código do caractere:

    • em 5 bits se é ^, $, #ou[a-z]
    • 11110[A-O]
    • 11111[P-Z]
Arnauld
fonte
Você já tentou outros algoritmos de compactação deflate? Há muita coisa na prateleira!
Dfeuer
Não existe uma regra que diga que ele deve funcionar no TIO!
dfeuer
O_o bom, pergunto-me se a compactação decimal ajudaria (basicamente o oposto de Huffman, o espaço é de 0 a 1, dividido em seções com tamanho arbitrário (<1, é claro), e a codificação é o número binário mais curto que se enquadra a fatia correta do espaço
somente ASCII
@ A codificação decimal somente ASCII (também conhecida como codificação aritmética) definitivamente deve melhorar a taxa de compactação, mas provavelmente por uma pequena margem em um fluxo de dados tão curto. Tenho certeza de que é possível melhorar a codificação de Huffman e / ou a função de previsão antes de mudar para a codificação aritmética (embora ambas sejam realmente básicas agora).
Arnauld
@ Somente ASCII Por exemplo, eu provavelmente deveria tentar códigos mais longos (usar nibbles é arbitrário). Eu também poderia adicionar um sinalizador de 1 bit no cabeçalho informando se os dados devem ser descompactados com os códigos Huffman estáticos padrão ou com códigos dinâmicos (se isso melhorar a compactação de alguns labirintos). Uma coisa que eu tentei foi girar o labirinto em 90 ° e ver se estava comprimindo melhor. Mas isso estava economizando 1 byte no geral.
Arnauld
4

R, pontuação 668 bytes

Isso tira proveito do fato de que o caráter da parede é determinado por seus arredores. Assim, os caracteres da parede podem ser codificados como bits. As informações restantes que precisam ser armazenadas são as dimensões do labirinto, as posições de início e fim e as posições de qualquer outro personagem que não seja da parede. Como os caracteres que não são da parede são ASCII, usei o bit mais significativo de cada byte para indicar se existe outro caractere a seguir, para que algumas das palavras nos labirintos não precisem ter o local de cada caractere armazenado separadamente. Observe também que para labirintos menores ou iguais a 256 caracteres (por exemplo, labirintos retangulares de até 16x16 ou equivalentes), as posições podem ser armazenadas em um byte, enquanto que para labirintos maiores as posições precisam de dois bytes.

Funções utilitárias

r <- as.raw

int_as_raw <- function(int, bytes = 2) {
  if (bytes == 1) {
    r(int)
  } else {
    do.call(c, lapply(int, function(.x) r(c(.x %/% 256, .x %% 256))))
  }
}

raw_as_int <- function(raw, bytes = 2) {
  if (bytes == 1) {
    as.integer(raw)
  } else {
    sapply(
      seq(1, length(raw) - 1, 2),
      function(.x) as.integer(as.integer(raw[.x + 0:1]) %*% c(256, 1))
    )
  }
}

Algoritmo de compressão

compress_maze <- function(maze) {
  maze_array <- do.call(rbind, strsplit(maze, ""))
  simple_maze <- r(maze_array %in% c("+", "#", "-", "|"))
  simple_maze <- packBits(c(simple_maze, rep(r(0), (8 - length(simple_maze)) %% 8)))
  maze_dim <- int_as_raw(dim(maze_array), 1)
  bytes_needed <- 1 + (length(maze_array) > 256)
  start_finish <- int_as_raw(sapply(c("^", "$"), function(.x) which(maze_array == .x)) - 1, bytes = bytes_needed)
  other_ascii_locs_rle <- rle(!(maze_array %in% c(" ", "+", "#", "-", "|", "$", "^")))
  other_ascii_locs <- cumsum(
    c(1, other_ascii_locs_rle$lengths[-length(other_ascii_locs_rle$lengths)])
  )[other_ascii_locs_rle$values]
  other_ascii_locs_length <- other_ascii_locs_rle$lengths[other_ascii_locs_rle$values]

  encode_ascii <- function(loc, len) {
    text <- charToRaw(paste(maze_array[loc:(loc + len - 1)], collapse = ""))
    if (len > 1) {
      text[1:(len - 1)] <- text[1:(len - 1)] | r(128)
    }
    c(int_as_raw(loc - 1, bytes = bytes_needed), text)
  }

  other_ascii_encoded <- Map(encode_ascii,
    other_ascii_locs,
    other_ascii_locs_length
    )
  other_ascii_encoded <- do.call(c, other_ascii_encoded)
  c(maze_dim, simple_maze, start_finish, other_ascii_encoded)
}

Algoritmo de descompressão

decompress_maze <- function(c_maze) {
  dim_maze <- as.integer(c_maze[1:2])
  len_maze <- prod(dim_maze)
  len_maze_b <- ceiling(len_maze / 8)
  bit_maze <- rawToBits(c_maze[-(1:2)])[1:len_maze]
  dim(bit_maze) <- dim_maze
  bit_maze[-1, ] <- bit_maze[-1, ] | rawShift(bit_maze[-nrow(bit_maze), ] & r(1), 1)
  bit_maze[-nrow(bit_maze), ] <- bit_maze[-nrow(bit_maze), ] | rawShift(bit_maze[-1, ] & r(1), 1)
  bit_maze[, -1] <- bit_maze[, -1] | rawShift(bit_maze[, -ncol(bit_maze)] & r(1), 2)
  bit_maze[, -ncol(bit_maze)] <- bit_maze[, -ncol(bit_maze)] | rawShift(bit_maze[, -1] & r(1), 2)
  bit_maze[(bit_maze & r(1)) == r(0)] <- r(0)
  array_maze <- c(" ", "#", "|", "-", "+")[(as.integer(bit_maze) + 1) %/% 2 + 1]
  dim(array_maze) <- dim_maze
  bytes_needed <- 1 + (len_maze > 256)
  start_finish <- raw_as_int(c_maze[2 + len_maze_b + 1:(bytes_needed * 2)], bytes_needed) + 1
  array_maze[start_finish] <- c("^", "$")
  i <- 3 + len_maze_b + 2 * bytes_needed
  while (i < length(c_maze)) {
    loc <- raw_as_int(c_maze[i + 1:bytes_needed - 1], bytes_needed) + 1
    i <- i + bytes_needed
    text <- character(0)
    while (c_maze[i] & r(128)) {
      text <- c(text, rawToChar(c_maze[i] & r(127)))
      i <- i + 1
    }
    text <- c(text, rawToChar(c_maze[i]))
    array_maze[loc:(loc + length(text) - 1)] <- text
    i <- i + 1
  }
  apply(array_maze, 1, paste, collapse = "")
}

Experimente online!

Nick Kennedy
fonte
Eu sabia que você seria capaz de armazenar as paredes como bits, mas eu gosto da sua abordagem para compactar os dados de posição de caracteres que não são da parede. +1
Neil