ASCII Maze Compression

9

Défi

Concevoir un algorithme de compression spécialisé pour compresser des labyrinthes ASCII. Vous devrez créer à la fois un algorithme de compression et un algorithme de décompression. Votre score sera basé sur la taille de vos labyrinthes compressés.

Labyrinthes

Ces labyrinthes sont faits principalement des personnages (étages), +, -, |et #(murs), et exactement un chacun ^(début) et $(fin). Ils peuvent également contenir des lettres ASCII, qui comptent comme des carreaux de sol. Aux fins de ce défi, les labyrinthes ne doivent pas nécessairement être résolubles et la signification réelle du contenu du labyrinthe n'est pas pertinente.

  • + sera utilisé pour les cellules de paroi où il y a au moins une cellule de paroi adjacente horizontalement et au moins une cellule de paroi adjacente verticalement.
  • | sera utilisé pour les cellules de paroi où il y a au moins une cellule de paroi adjacente verticalement, mais aucune cellule de paroi adjacente horizontalement.
  • - sera utilisé pour les cellules de paroi où il y a au moins une cellule de paroi adjacente horizontalement, mais aucune cellule de paroi adjacente verticalement
  • # ne sera utilisé que pour les cellules de paroi qui ne sont pas orthogonalement adjacentes à d'autres cellules de paroi.

Tous les labyrinthes sont rectangulaires, mais n'ont pas nécessairement un alignement régulier grille / mur.

Labyrinthes à compresser

Labyrinthe 1

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

Labyrinthe 2

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

Labyrinthe 3

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

Labyrinthe 4

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

Labyrinthe 5

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

Labyrinthe 6

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

Labyrinthe 7

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

Labyrinthe 8

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

Labyrinthe 9

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

Labyrinthe 10

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

Règles, hypothèses, notation

  • Les failles standard sont interdites
    • Écrivez un programme général, pas un qui ne fonctionne que pour les dix cas de test. Il doit être capable de gérer n'importe quel labyrinthe arbitraire.
  • Vous pouvez supposer qu'il y aura exactement une entrée et une sortie. Les entrées et sorties seront toujours à la frontière du labyrinthe.
  • Vous pouvez supposer que toutes les entrées utilisent des murs qui suivent les règles énumérées ci-dessus. Votre algorithme de compression n'a pas à fonctionner pour les labyrinthes contenant des murs qui violent ces règles.
  • Les labyrinthes d'entrée peuvent être résolus ou non.
  • Vous pouvez supposer que le labyrinthe ne dépassera pas 100 caractères dans les deux sens.
  • Vous pouvez supposer que les lettres n'apparaîtront pas sur le bord du labyrinthe. (puisque c'est le cas pour les exemples fournis)
  • Votre score est la taille totale, en octets (octets), de tous les labyrinthes compressés.
    • Vous pouvez utiliser hexadécimal, base64, chaînes binaires ou tout autre format similaire comme représentation de votre labyrinthe compressé si cela vous semble plus pratique. Vous devez toujours compter le résultat en octets entiers, arrondi à chaque labyrinthe (par exemple, 4 chiffres de base64 est de 3 octets, 2 chiffres hexadécimaux est de 1 octet, 8 chiffres binaires est de 1 octet, etc ...)
    • Le score le plus bas gagne!
Beefster
la source
Y a-t-il une limite de taille pour un labyrinthe?
Incarnation de l'ignorance
@EmbodimentofIgnorance 100x100
Beefster
@Arnauld était en fait un problème de copier-coller, mais je pense que le formatage SE supprime les espaces à la fin de la ligne de toute façon. Oui, il est censé être rembourré d'espace.
Beefster
@ChasBrown, qui compte comme une faille standard, ce qui signifie qu'il est interdit par défaut.
Beefster
1
@schnaader, cela semble raisonnable compte tenu des exemples de cas de test.
Beefster

Réponses:

5

JavaScript (Node.js) , score =  586 541 503 492  479 octets

Les murs sont stockés sous la forme d'un flux de bits codé par Huffman décrivant si une fonction de prédiction renvoie la supposition correcte ou non.

(,c)c

Essayez-le en ligne!

Commun

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

Compression

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

Décompression

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

Comment?

Un labyrinthe est codé comme un flux binaire qui est finalement converti en chaîne.

Entête

L'en-tête comprend:

  • w
  • h

Données de mur

01

01

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

WnPnCn

Wn=PnCn

Les formes finales des murs sont déduites d'une manière similaire à la réponse de Nick Kennedy .

Caractères spéciaux

Chaque caractère spécial est codé comme suit:

  • 1

    • 63
    • 111111
  • Le code du personnage:

    • sur 5 bits si elle est ^, $, #ou[a-z]
    • 11110[A-O]
    • 11111[P-Z]
Arnauld
la source
Avez-vous essayé d'autres algorithmes de compression que deflate? Il y en a énormément sur l'étagère!
dfeuer
Il n'y a aucune règle qui dit que cela doit fonctionner dans TIO!
dfeuer
O_o bien, je me demande si la compression décimale aiderait du tout (fondamentalement l'opposé de huffman, l'espace est de 0 à 1, divisé en sections de taille arbitraire (<1 bien sûr), et l'encodage est le nombre binaire le plus court qui relève de la bonne tranche de l'espace
ASCII uniquement
@ Le codage décimal ASCII uniquement (ou codage arithmétique) devrait certainement améliorer le taux de compression, mais probablement d'une petite marge sur un flux de données aussi court. Je suis sûr qu'il est possible d'améliorer le codage Huffman et / ou la fonction de prédiction avant de passer au codage arithmétique (les deux étant vraiment basiques en ce moment).
Arnauld
@ ASCII uniquement Par exemple, je devrais probablement essayer des codes plus longs (l'utilisation de grignotages est arbitraire). Je pourrais également ajouter un indicateur 1 bit dans l'en-tête indiquant si les données doivent être décompressées avec les codes Huffman statiques par défaut ou avec des codes dynamiques (si cela s'avère améliorer la compression de certains labyrinthes). Une chose que j'ai essayée était de faire pivoter le labyrinthe de 90 ° et de voir si la compression était meilleure. Mais cela ne faisait qu'économiser 1 octet au total.
Arnauld
4

R, score 668 octets

Cela profite du fait que le caractère du mur est déterminé par son environnement. En tant que tels, les caractères du mur peuvent être codés en bits. Les informations restantes qui doivent être stockées sont les dimensions du labyrinthe, les positions de début et de fin et les positions de tout autre personnage non-mur. Étant donné que les caractères non muraux sont ASCII, j'ai utilisé le bit le plus significatif de chaque octet pour indiquer s'il existe un autre caractère qui suit afin que certains des mots dans les labyrinthes n'aient pas besoin d'avoir l'emplacement de chaque caractère stocké séparément. Notez également que pour les labyrinthes inférieurs ou égaux à 256 caractères (par exemple jusqu'à 16x16 ou les labyrinthes équivalents rectangulaires), les positions peuvent être stockées dans un octet, tandis que pour les labyrinthes plus grands, les positions nécessitent deux octets.

Fonctions utilitaires

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

Algorithme de compression

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

Algorithme de décompression

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

Essayez-le en ligne!

Nick Kennedy
la source
Je savais que vous seriez en mesure de stocker les murs sous forme de bits, mais j'aime votre approche pour compresser les données de position des caractères non-mur. +1
Neil