Compresión de laberinto ASCII

9

Desafío

Diseñe un algoritmo de compresión especializado para comprimir laberintos ASCII. Deberá crear un algoritmo de compresión y un algoritmo de descompresión. Su puntaje se basará en el tamaño de sus laberintos comprimidos.

Laberintos

Estos laberintos están hechos principalmente de los personajes (pisos), +, -, |, y #(paredes), y exactamente un cada uno de ^(START) y $(fin). También pueden contener letras ASCII, que cuentan como baldosas. Para los propósitos de este desafío, los laberintos no tienen que ser solucionables y el significado real del contenido del laberinto es irrelevante.

  • + se usará para celdas de pared donde hay al menos una celda de pared adyacente horizontalmente y al menos una celda de pared adyacente verticalmente.
  • | se usará para celdas de pared donde hay al menos una celda de pared adyacente verticalmente, pero no hay celdas de pared adyacentes horizontalmente.
  • - se usará para celdas de pared donde hay al menos una celda de pared adyacente horizontalmente, pero no hay celdas de pared adyacentes verticalmente
  • # solo se usará para células de pared que no estén ortogonalmente adyacentes a otras células de pared.

Todos los laberintos son rectangulares, pero no necesariamente tienen una alineación regular de cuadrícula / pared.

Laberintos Para Comprimir

Laberinto 1

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

Laberinto 2

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

Laberinto 3

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

Laberinto 4

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

Laberinto 5

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

Laberinto 6

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

Laberinto 7

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

Laberinto 8

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

Laberinto 9

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

Laberinto 10

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

Reglas, supuestos, puntuación

  • Las lagunas estándar están prohibidas
    • Escriba un programa general, no uno que solo funcione para los diez casos de prueba. Debe ser capaz de manejar cualquier laberinto arbitrario.
  • Puede suponer que habrá exactamente una entrada y una salida. Las entradas y salidas siempre estarán en el borde del laberinto.
  • Puede suponer que todas las entradas usan paredes que siguen las reglas enumeradas anteriormente. Su algoritmo de compresión no tiene que funcionar para laberintos que contienen paredes que violan esas reglas.
  • Los laberintos de entrada pueden o no tener solución.
  • Puede suponer que el laberinto no tendrá más de 100 caracteres en cualquier dirección.
  • Puede suponer que las letras no aparecerán en el borde del laberinto. (ya que este es el caso de los ejemplos proporcionados)
  • Su puntaje es el tamaño total, en bytes (octetos), de todos los laberintos comprimidos.
    • Puede usar hexadecimal, base64, cadenas binarias o cualquier formato similar como representación para su laberinto comprimido si lo encuentra más conveniente. Todavía debe contar el resultado en octetos completos, redondeados para cada laberinto (por ejemplo, 4 dígitos de base64 son 3 bytes, 2 dígitos hexadecimales son 1 byte, 8 dígitos binarios son 1 byte, etc.)
    • ¡La puntuación más baja gana!
Carne de res
fuente
¿Hay un límite de tamaño para un laberinto?
Encarnación de la ignorancia
@EmbodimentofIgnorance 100x100
Beefster
@Arnauld en realidad fue un problema de copiado, pero creo que el formato SE elimina espacios al final de la línea de todos modos. Sí, se supone que debe estar acolchado.
Beefster
@ChasBrown, eso cuenta como un vacío legal estándar, lo que significa que está prohibido de forma predeterminada.
Beefster
1
@schnaader, eso parece razonable dados los ejemplos de casos de prueba.
Beefster

Respuestas:

5

JavaScript (Node.js) , puntuación =  586 541 503 492  479 bytes

Las paredes se almacenan como una secuencia de bits codificada por Huffman que describe si una función de predicción está devolviendo la suposición correcta o no.

(d,c)dc

Pruébalo en línea!

Común

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

Compresión

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

Descompresión

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

¿Cómo?

Un laberinto se codifica como un flujo de bits que finalmente se convierte en una cadena.

Encabezamiento

El encabezado consta de:

  • w
  • h

Datos de la pared

01

01

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

WnPnCn

Wn=PnCn

Las formas finales de la pared se deducen de manera similar a la respuesta de Nick Kennedy .

Caracteres especiales

Cada carácter especial se codifica como:

  • 1

    • 63
    • 111111
  • El código del personaje:

    • en 5 bits si se trata de ^, $, #o[a-z]
    • 11110[A-O]
    • 11111[P-Z]
Arnauld
fuente
¿Has probado otros algoritmos de compresión deflate? ¡Hay muchísimo en el estante!
dfeuer
¡No hay una regla que diga que tiene que funcionar en TIO!
dfeuer
O_o bien, me pregunto si la compresión decimal ayudaría en absoluto (básicamente lo contrario de huffman, el espacio es 0 a 1, dividido en secciones con un tamaño arbitrario (<1, por supuesto), y la codificación es el número binario más corto que cae dentro de la porción correcta del espacio
solo ASCII
@ La codificación decimal solo ASCII (también conocida como codificación aritmética) definitivamente debería mejorar la relación de compresión, pero probablemente por un pequeño margen en un flujo de datos tan corto. Sin embargo, estoy seguro de que es posible mejorar la codificación Huffman y / o la función de predicción antes de cambiar a la codificación aritmética (ambas son realmente básicas en este momento).
Arnauld
@ Solo ASCII Por ejemplo, probablemente debería probar códigos más largos (el uso de nibbles es arbitrario). También podría agregar una bandera de 1 bit en el encabezado que indica si los datos deben desempaquetarse con los códigos estáticos predeterminados de Huffman o con códigos dinámicos (si resulta que mejora la compresión de algunos laberintos). Una cosa que intenté fue rotar el laberinto 90 ° y ver si se comprimía mejor. Pero eso solo estaba ahorrando 1 byte en general.
Arnauld
4

R, puntaje 668 bytes

Esto aprovecha el hecho de que el carácter del muro está determinado por su entorno. Como tal, los caracteres de la pared se pueden codificar como bits. La información restante que debe almacenarse son las dimensiones del laberinto, las posiciones de inicio y finalización y las posiciones de cualquier otro personaje que no sea de muro. Como los caracteres que no son de muro son ASCII, he usado el bit más significativo de cada byte para indicar si hay otro carácter que sigue para que algunas de las palabras en los laberintos no tengan que almacenar la ubicación de cada carácter. por separado. Tenga en cuenta también que para laberintos menores o iguales a 256 caracteres (por ejemplo, hasta 16x16 o laberintos rectangulares equivalentes), las posiciones se pueden almacenar en un byte, mientras que para laberintos más grandes las posiciones necesitan dos bytes.

Funciones de utilidad

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 compresión

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 descompresión

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 = "")
}

Pruébalo en línea!

Nick Kennedy
fuente
Sabía que sería capaz de almacenar los muros como bits, pero me gusta su enfoque para comprimir los datos de posición de los personajes que no son muros. +1
Neil