Construya un Optimizador de Magnitud No Gráfica ™

12

Un nonograma es un juego de rompecabezas japonés en el que el objetivo es dibujar una imagen en blanco y negro de acuerdo con una lista de regiones contiguas, así:

Un nonograma de ejemplo con una "lambda".

Defina la magnitud no gráfica de una fila o columna para que sea el número de regiones negras contiguas en esa fila o columna. Por ejemplo, la fila superior tiene una magnitud no gráfica de 1, porque hay una región de 2 cuadrados en esa fila. La octava fila tiene una magnitud no gráfica de 3 porque tiene 2, 2, 1.

Una fila o columna vacía tiene una magnitud no gráfica de 0.


Su tarea es escribir un programa que tome una cuadrícula de solución para un nonograma, y ​​produzca una cuadrícula de solución con el menor número posible de cuadrados rellenados donde cada fila y columna tenga la misma magnutida no gráfica que la cuadrícula de solución dada.

Por ejemplo, una cuadrícula de nonograma con todos los cuadrados rellenos tiene una magnitud no gráfica de 1 en cada fila o columna:

Un nonograma de 10x10 donde se llena cada cuadrado.

Se podría lograr la misma magnitud no gráfica simplemente con una franja diagonal a través de la cuadrícula, reduciendo drásticamente el número de cuadrados rellenados:

Un nonograma de 10x10 con la misma magnitud no gráfica que la anterior.


Su programa recibirá una entrada que consta de 50,000 líneas de este archivo ( archivo de texto tar.gz de 1.32 MB; 2.15 MB descomprimido), cada una representando una cuadrícula de solución de nonograma de 16 × 16 con cuadrados rellenados aleatoriamente (80% negro), y genera otras 50,000 líneas, cada una de las cuales contiene la cuadrícula de solución optimizada para la cuadrícula de entrada correspondiente.

Cada cuadrícula se representa como una cadena base64 con 43 caracteres (codificación de cuadrados de izquierda a derecha, luego de arriba a abajo), y su programa deberá devolver su salida en el mismo formato. Por ejemplo, la primera cuadrícula del archivo es E/lu/+7/f/3rp//f799xn/9//2mv//nvj/bt/yc9/40=y se representa como:

primer ejemplo nonograma

La cuadrícula comienza con los Emapas a 000100, por lo que las primeras seis celdas en la fila superior son todas blancas excepto la cuarta. El siguiente carácter es el /que se asigna 111111, por lo que las siguientes 6 celdas son todas negras, y así sucesivamente.


En realidad, su programa debe devolver una cuadrícula de solución con las magnitudes no gráficas correctas para cada uno de los 50,000 casos de prueba. Se permite devolver la misma cuadrícula que la entrada si no se encontró nada mejor.

El programa para devolver la menor cantidad de casillas completas (en cualquier idioma) es el ganador, con un código más corto que es el desempate.


Marcador actual:

  1. 3,637,260 - Sleafar, Java
  2. 7.270.894 - flawr, Matlab
  3. 10,239,288 - Joe Z., Bash
Joe Z.
fuente
1
Realmente no veo el punto de codificación de la base 64 y trabajar con eso es un verdadero dolor. ¿No sería más fácil hacer líneas de unos y ceros? ¿O codificar todo como un mapa de bits?
error
@flawr: Reduce el tamaño del archivo, principalmente (en un factor de 6 en comparación con solo 1 y 0). Además, los mapas de bits serían aún más difíciles de trabajar.
Joe Z.
Simplemente puede hacer una imagen en blanco y negro, fácil de leer / escribir y el mismo tamaño que la codificación b64.
error
2
Tampoco soy un fanático de la codificación b64, para entrada y / o salida. ¿Por qué no dejar que la E / S esté en cualquier formato conveniente?
Sparr
1
Asumiendo que sí, debería tener una solución óptima mañana a esta hora.
quintopia

Respuestas:

7

Python 2 y PuLP: 2,644,688 cuadrados (óptimamente minimizados); 10,753,553 cuadrados (óptimamente maximizados)

Mínimamente golfizado a 1152 bytes

from pulp import*
x=0
f=open("c","r")
g=open("s","w")
for k,m in enumerate(f):
 if k%2:
    b=map(int,m.split())
    p=LpProblem("Nn",LpMinimize)
    q=map(str,range(18))
    ir=q[1:18]
    e=LpVariable.dicts("c",(q,q),0,1,LpInteger)
    rs=LpVariable.dicts("rs",(ir,ir),0,1,LpInteger)
    cs=LpVariable.dicts("cs",(ir,ir),0,1,LpInteger)
    p+=sum(e[r][c] for r in q for c in q),""
    for i in q:p+=e["0"][i]==0,"";p+=e[i]["0"]==0,"";p+=e["17"][i]==0,"";p+=e[i]["17"]==0,""
    for o in range(289):i=o/17+1;j=o%17+1;si=str(i);sj=str(j);l=e[si][str(j-1)];ls=rs[si][sj];p+=e[si][sj]<=l+ls,"";p+=e[si][sj]>=l-ls,"";p+=e[si][sj]>=ls-l,"";p+=e[si][sj]<=2-ls-l,"";l=e[str(i-1)][sj];ls=cs[si][sj];p+=e[si][sj]<=l+ls,"";p+=e[si][sj]>=l-ls,"";p+=e[si][sj]>=ls-l,"";p+=e[si][sj]<=2-ls-l,""
    for r,z in enumerate(a):p+=lpSum([rs[str(r+1)][c] for c in ir])==2*z,""
    for c,z in enumerate(b):p+=lpSum([cs[r][str(c+1)] for r in ir])==2*z,""
    p.solve()
    for r in ir:
     for c in ir:g.write(str(int(e[r][c].value()))+" ")
     g.write('\n')
    g.write('%d:%d\n\n'%(-~k/2,value(p.objective)))
    x+=value(p.objective)
 else:a=map(int,m.split())
print x

(Nota: las líneas muy sangradas comienzan con pestañas, no espacios).

Salida de ejemplo: https://drive.google.com/file/d/0B-0NVE9E8UJiX3IyQkJZVk82Vkk/view?usp=sharing

Resulta que problemas como son fácilmente convertibles en programas lineales enteros, y necesitaba un problema básico para aprender a usar PuLP, una interfaz de Python para una variedad de solucionadores de LP, para un proyecto propio. También resulta que PuLP es extremadamente fácil de usar, y el generador de LP sin golf funcionó perfectamente la primera vez que lo probé.

Las dos cosas buenas de emplear un solucionador de IP de ramificación y enlace para hacer el arduo trabajo de resolver esto por mí (aparte de no tener que implementar un solucionador de ramificación y enlace) son que

  • Los solucionadores especialmente diseñados son realmente rápidos. Este programa resuelve todos los problemas de 50000 en aproximadamente 17 horas en mi PC hogareña relativamente baja. Cada instancia tomó de 1-1.5 segundos para resolver.
  • Producen soluciones óptimas garantizadas (o le dicen que no lo hicieron). Por lo tanto, puedo estar seguro de que nadie superará mi puntaje en cuadros (aunque alguien podría empatarlo y vencerme en la parte de golf).

Cómo usar este programa

Primero, necesitará instalar PuLP. pip install pulpdebería hacer el truco si tienes pip instalado.

Luego, deberá colocar lo siguiente en un archivo llamado "c": https://drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp=sharing

Luego, ejecute este programa en cualquier compilación de Python 2 tardía desde el mismo directorio. En menos de un día, tendrá un archivo llamado "s" que contiene 50,000 cuadrículas de nonogramas resueltas (en formato legible), cada una con el número total de cuadrados rellenos enumerados debajo.

Si desea maximizar el número de cuadrados rellenos en su lugar, cambie la LpMinimizelínea 8 a LpMaximize. Obtendrá una salida muy similar a esta: https://drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=sharing

Formato de entrada

Este programa utiliza un formato de entrada modificado, ya que Joe Z. dijo que se nos permitiría volver a codificar el formato de entrada si quisiéramos en un comentario sobre el OP. Haga clic en el enlace de arriba para ver cómo se ve. Se compone de 10000 líneas, cada una con 16 números. Las líneas pares son las magnitudes de las filas de una instancia determinada, mientras que las líneas impares son las magnitudes de las columnas de la misma instancia que la línea que se encuentra sobre ellas. Este archivo fue generado por el siguiente programa:

from bitqueue import *

with open("nonograms_b64.txt","r") as f:
    with open("nonogram_clues.txt","w") as g:
        for line in f:
            q = BitQueue(line.decode('base64'))
            nonogram = []
            for i in range(256):
                if not i%16: row = []
                row.append(q.nextBit())
                if not -~i%16: nonogram.append(row)
            s=""
            for row in nonogram:
                blocks=0                         #magnitude counter
                for i in range(16):
                    if row[i]==1 and (i==0 or row[i-1]==0): blocks+=1
                s+=str(blocks)+" "
            print >>g, s
            nonogram = map(list, zip(*nonogram)) #transpose the array to make columns rows
            s=""
            for row in nonogram:
                blocks=0
                for i in range(16):
                    if row[i]==1 and (i==0 or row[i-1]==0): blocks+=1
                s+=str(blocks)+" "
            print >>g, s

(Este programa de re-codificación también me dio una oportunidad adicional para probar mi clase personalizada de BitQueue que creé para el mismo proyecto mencionado anteriormente. Es simplemente una cola a la cual los datos pueden ser empujados como secuencias de bits O bytes, y desde los cuales los datos pueden aparecer un bit o un byte a la vez. En este caso, funcionó perfectamente).

Re-codifiqué la entrada por la razón específica de que para construir un ILP, la información adicional sobre las cuadrículas que se usaron para generar las magnitudes es perfectamente inútil. Las magnitudes son las únicas restricciones, por lo que las magnitudes son todo lo que necesitaba acceso.

Constructor ILP sin golf

from pulp import *
total = 0
with open("nonogram_clues.txt","r") as f:
    with open("solutions.txt","w") as g:
        for k,line in enumerate(f):
            if k%2:
                colclues=map(int,line.split())
                prob = LpProblem("Nonogram",LpMinimize)
                seq = map(str,range(18))
                rows = seq
                cols = seq
                irows = seq[1:18]
                icols = seq[1:18]
                cells = LpVariable.dicts("cell",(rows,cols),0,1,LpInteger)
                rowseps = LpVariable.dicts("rowsep",(irows,icols),0,1,LpInteger)
                colseps = LpVariable.dicts("colsep",(irows,icols),0,1,LpInteger)
                prob += sum(cells[r][c] for r in rows for c in cols),""
                for i in rows:
                    prob += cells["0"][i] == 0,""
                    prob += cells[i]["0"] == 0,""
                    prob += cells["17"][i] == 0,""
                    prob += cells[i]["17"] == 0,""
                for i in range(1,18):
                    for j in range(1,18):
                        si = str(i); sj = str(j)
                        l = cells[si][str(j-1)]; ls = rowseps[si][sj]
                        prob += cells[si][sj] <= l + ls,""
                        prob += cells[si][sj] >= l - ls,""
                        prob += cells[si][sj] >= ls - l,""
                        prob += cells[si][sj] <= 2 - ls - l,""
                        l = cells[str(i-1)][sj]; ls = colseps[si][sj]
                        prob += cells[si][sj] <= l + ls,""
                        prob += cells[si][sj] >= l - ls,""
                        prob += cells[si][sj] >= ls - l,""
                        prob += cells[si][sj] <= 2 - ls - l,""
                for r,clue in enumerate(rowclues):
                    prob += lpSum([rowseps[str(r+1)][c] for c in icols]) == 2 * clue,""
                for c,clue in enumerate(colclues):
                    prob += lpSum([colseps[r][str(c+1)] for r in irows]) == 2 * clue,""
                prob.solve()
                print "Status for problem %d: "%(-~k/2),LpStatus[prob.status]
                for r in rows[1:18]:
                    for c in cols[1:18]:
                        g.write(str(int(cells[r][c].value()))+" ")
                    g.write('\n')
                g.write('Filled squares for %d: %d\n\n'%(-~k/2,value(prob.objective)))
                total += value(prob.objective)
            else:
                rowclues=map(int,line.split())
print "Total number of filled squares: %d"%total

Este es el programa que realmente produjo el "resultado de ejemplo" vinculado anteriormente. De ahí las cuerdas extra largas al final de cada cuadrícula, que trunqué al jugar golf. (La versión de golf debería producir un resultado idéntico, menos las palabras "Filled squares for ")

Cómo funciona

cells = LpVariable.dicts("cell",(rows,cols),0,1,LpInteger)
rowseps = LpVariable.dicts("rowsep",(irows,icols),0,1,LpInteger)
colseps = LpVariable.dicts("colsep",(irows,icols),0,1,LpInteger)

Utilizo una cuadrícula de 18x18, con la parte central de 16x16 como la solución de rompecabezas real. cellses esta cuadrícula La primera línea crea 324 variables binarias: "cell_0_0", "cell_0_1", y así sucesivamente. También creo cuadrículas de los "espacios" entre y alrededor de las celdas en la parte de solución de la cuadrícula. rowsepsapunta a las 289 variables que simbolizan los espacios que separan las celdas horizontalmente, mientras que de colsepsmanera similar apunta a las variables que marcan los espacios que separan las celdas verticalmente. Aquí hay un diagrama unicode:

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|□|0
  - - - - - - - - - - - - - - - - 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Los 0sys son los valores binarios rastreados por las cellvariables, los |s son los valores binarios rastreados por las rowsepvariables y los -s son los valores binarios rastreados por las colsepvariables.

prob += sum(cells[r][c] for r in rows for c in cols),""

Esta es la función objetivo. Solo la suma de todas las cellvariables. Como se trata de variables binarias, esta es exactamente la cantidad de cuadrados rellenos en la solución.

for i in rows:
    prob += cells["0"][i] == 0,""
    prob += cells[i]["0"] == 0,""
    prob += cells["17"][i] == 0,""
    prob += cells[i]["17"] == 0,""

Esto solo establece las celdas alrededor del borde exterior de la cuadrícula a cero (por eso las representé como ceros arriba). Esta es la forma más conveniente de rastrear cuántos "bloques" de celdas se llenan, ya que garantiza que cada cambio de lleno a lleno (moviéndose a través de una columna o fila) coincida con un cambio correspondiente de llenado a vacío (y viceversa) ), incluso si se llena la primera o la última celda de la fila. Esta es la única razón para usar una cuadrícula de 18x18 en primer lugar. No es la única forma de contar bloques, pero creo que es la más simple.

for i in range(1,18):
    for j in range(1,18):
        si = str(i); sj = str(j)
        l = cells[si][str(j-1)]; ls = rowseps[si][sj]
        prob += cells[si][sj] <= l + ls,""
        prob += cells[si][sj] >= l - ls,""
        prob += cells[si][sj] >= ls - l,""
        prob += cells[si][sj] <= 2 - ls - l,""
        l = cells[str(i-1)][sj]; ls = colseps[si][sj]
        prob += cells[si][sj] <= l + ls,""
        prob += cells[si][sj] >= l - ls,""
        prob += cells[si][sj] >= ls - l,""
        prob += cells[si][sj] <= 2 - ls - l,""

Esta es la verdadera carne de la lógica de la ILP. Básicamente requiere que cada celda (que no sean las de la primera fila y columna) sea el xor lógico de la celda y el separador directamente a su izquierda en su fila y directamente encima de ella en su columna. Obtuve las restricciones que simulan un xor dentro de un programa entero {0,1} de esta maravillosa respuesta: /cs//a/12118/44289

Para explicar un poco más: esta restricción xor hace que los separadores puedan ser 1 si y solo si se encuentran entre celdas que son 0 y 1 (marcando un cambio de vacío a lleno o viceversa). Por lo tanto, habrá exactamente el doble de separadores de 1 valor en una fila o columna que el número de bloques en esa fila o columna. En otras palabras, la suma de los separadores en una fila o columna dada es exactamente el doble de la magnitud de esa fila / columna. De ahí las siguientes restricciones:

for r,clue in enumerate(rowclues):
    prob += lpSum([rowseps[str(r+1)][c] for c in icols]) == 2 * clue,""
for c,clue in enumerate(colclues):
    prob += lpSum([colseps[r][str(c+1)] for r in irows]) == 2 * clue,""

Y eso es todo. El resto solo le pide al solucionador predeterminado que resuelva el ILP, luego formatea la solución resultante a medida que la escribe en el archivo.

quintapia
fuente
Muy buena respuesta. Hazme querer aprender sobre solucionadores de LP. ¿Crees que podría usarse para resolver el rompecabezas de inundación (enlace) para un tablero de 6 colores de 19x19 (con respecto al tiempo para calcular una solución)? Ya he respondido a ese concurso (y lo gané), sin embargo, mi método (algoritmo de búsqueda A *) solo da soluciones no óptimas.
tigrou
@tigrou Gracias. No estoy seguro de que el problema de la inundación sea lo suficientemente lineal como para admitir tal solución. Ciertamente no puedo ver cómo modelarlo de esta manera.
quintopia
Parece que alguien ya lo intentó: kunigami.blog/2012/09/16/flood-it-an-exact-approach Sin embargo, no pudieron obtener soluciones óptimas en un tiempo factible para una placa de 14x14.
tigrou
3

Java, 6,093,092 4,332,656 3,637,260 cuadrados (minimizado), 10,567,550 10,567,691 10,568,746 cuadrados (maximizado)

Ambas variantes del programa realizan repetidamente operaciones en la cuadrícula de origen, sin cambiar la magnitud.

Minimizador

encogimiento()

ingrese la descripción de la imagen aquí

Si un cuadrado negro tiene 2 vecinos blancos y 2 vecinos negros en un ángulo de 90 °, puede reemplazarse por un cuadrado blanco.

moveLine ()

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

En configuraciones como la anterior, la línea negra se puede mover hacia la derecha. Esto se hace repetidamente para las 4 direcciones de línea en sentido horario y antihorario, para abrir nuevas posibilidades de contracción.

Maximizador

Descomente la línea main()y comente la línea que se encuentra arriba para esta versión.

crecer()

ingrese la descripción de la imagen aquí

Si un cuadrado blanco tiene 2 vecinos blancos y 2 vecinos negros en un ángulo de 90 °, puede reemplazarse por un cuadrado negro.

moveLine ()

Igual que en Minimizer.

Fuente

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.Arrays;
import java.util.Base64;
import java.util.function.Function;

public class Main {
    private static final int SIZE = 16;
    private static final int SIZE_4 = SIZE + 4;
    private static final int E = 0;
    private static final int N = 1;
    private static final int W = 2;
    private static final int S = 3;

    private static final Base64.Decoder decoder = Base64.getMimeDecoder();
    private static final Base64.Encoder encoder = Base64.getMimeEncoder();
    private static int sourceBlack = 0;
    private static int targetBlack = 0;

    private static class Nonogram {
        private final boolean[] cells = new boolean[SIZE_4 * SIZE_4];
        private final int[] magnitudes;

        public Nonogram(String encoded) {
            super();
            byte[] decoded = decoder.decode(encoded);
            for (int i = 0; i < decoded.length; ++ i) {
                for (int j = 0; j < 8; ++ j) {
                    if ((decoded[i] & (1 << (7 - j))) != 0) {
                        int k = i * 8 + j;
                        cells[getPos(k / SIZE, k % SIZE)] = true;
                        ++ sourceBlack;
                    }
                }
            }
            magnitudes = calcMagnitudes();
        }

        private int getPos(int row, int col) {
            return (row + 2) * SIZE_4 + col + 2;
        }

        private int move(int pos, int dir, int count) {
            switch (dir) {
                case E: return pos + count;
                case N: return pos - count * SIZE_4;
                case W: return pos - count;
                case S: return pos + count * SIZE_4;
                default: return pos;
            }
        }

        private int move(int pos, int dir) {
            return move(pos, dir, 1);
        }

        private int[] calcMagnitudes() {
            int[] result = new int[SIZE * 2];
            for (int row = 0; row < SIZE; ++ row) {
                for (int col = 0; col < SIZE; ++ col) {
                    int pos = getPos(row, col);
                    if (cells[pos]) {
                        if (!cells[move(pos, W)]) {
                            ++ result[row + SIZE];
                        }
                        if (!cells[move(pos, N)]) {
                            ++ result[col];
                        }
                    }
                }
            }
            return result;
        }

        private boolean isBlack(int pos) {
            return cells[pos];
        }

        private boolean isWhite(int pos) {
            return !cells[pos];
        }

        private boolean allBlack(int pos, int dir, int count) {
            int p = pos;
            for (int i = 0; i < count; ++ i) {
                if (isWhite(p)) {
                    return false;
                }
                p = move(p, dir);
            }
            return true;
        }

        private boolean allWhite(int pos, int dir, int count) {
            int p = pos;
            for (int i = 0; i < count; ++ i) {
                if (isBlack(p)) {
                    return false;
                }
                p = move(p, dir);
            }
            return true;
        }

        private int findWhite(int pos, int dir) {
            int count = 0;
            int p = pos;
            while (cells[p]) {
                ++ count;
                p = move(p, dir);
            }
            return count;
        }

        @SafeVarargs
        private final void forEach(Function<Integer, Boolean>... processors) {
            outer:
            for (;;) {
                for (Function<Integer, Boolean> processor : processors) {
                    for (int row = 0; row < SIZE; ++ row) {
                        for (int col = 0; col < SIZE; ++ col) {
                            if (processor.apply(getPos(row, col))) {
                                continue outer;
                            }
                        }
                    }
                }
                return;
            }
        }

        private boolean shrink(int pos) {
            if (cells[pos] && cells[move(pos, W)] != cells[move(pos, E)] &&
                    cells[move(pos, N)] != cells[move(pos, S)]) {
                cells[pos] = false;
                return true;
            }
            return false;
        }

        private boolean grow(int pos) {
            if (!cells[pos] && cells[move(pos, W)] != cells[move(pos, E)] &&
                    cells[move(pos, N)] != cells[move(pos, S)]) {
                cells[pos] = true;
                return true;
            }
            return false;
        }

        private boolean moveLine(boolean clockwise, int dir, int sourcePos) {
            int from = (dir + (clockwise ? 1 : 3)) % 4;
            int to = (dir + (clockwise ? 3 : 1)) % 4;
            int opp = (dir + 2) % 4;
            if (isBlack(sourcePos) && isWhite(move(sourcePos, from)) && isWhite(move(sourcePos, dir))) {
                int toCount = findWhite(move(move(sourcePos, dir), to), to) + 1;
                if (allWhite(move(sourcePos, to), to, toCount + 1)) {
                    int lineCount = 1;
                    int tmpPos = move(sourcePos, opp);
                    while (isBlack(tmpPos) && isWhite(move(tmpPos, from)) && allWhite(move(tmpPos, to),  to, toCount + 1)) {
                        ++ lineCount;
                        tmpPos = move(tmpPos, opp);
                    }
                    if (allBlack(tmpPos, to, toCount + 1)) {
                        tmpPos = sourcePos;
                        for (int j = 0; j < lineCount; ++ j) {
                            cells[tmpPos] = false;
                            cells[move(tmpPos, to, toCount)] = true;
                            tmpPos = move(tmpPos, opp);
                        }
                        return true;
                    }
                }
            }
            return false;
        }

        public Nonogram minimize() {
            for (int i = 0; i < 5; ++ i) {
                forEach(pos -> shrink(pos), pos -> moveLine(true, E, pos), pos -> moveLine(true, N, pos),
                        pos -> moveLine(true, W, pos), pos -> moveLine(true, S, pos));
                forEach(pos -> shrink(pos), pos -> moveLine(false, E, pos), pos -> moveLine(false, N, pos),
                        pos -> moveLine(false, W, pos), pos -> moveLine(false, S, pos));
            }
            return this;
        }

        public Nonogram maximize() {
            for (int i = 0; i < 5; ++ i) {
                forEach(pos -> grow(pos), pos -> moveLine(true, E, pos), pos -> moveLine(true, N, pos),
                        pos -> moveLine(true, W, pos), pos -> moveLine(true, S, pos));
                forEach(pos -> grow(pos), pos -> moveLine(false, E, pos), pos -> moveLine(false, N, pos),
                        pos -> moveLine(false, W, pos), pos -> moveLine(false, S, pos));
            }
            return this;
        }

        public String toBase64() {
            if (!Arrays.equals(magnitudes, calcMagnitudes())) {
                throw new RuntimeException("Something went wrong!");
            }
            byte[] decoded = new byte[SIZE * SIZE / 8];
            for (int i = 0; i < decoded.length; ++ i) {
                for (int j = 0; j < 8; ++ j) {
                    int k = i * 8 + j;
                    if (cells[getPos(k / SIZE, k % SIZE)]) {
                        decoded[i] |= 1 << (7 - j);
                        ++ targetBlack;
                    }
                }
            }
            return encoder.encodeToString(decoded);
        }

        @Override
        public String toString() {
            StringBuilder b = new StringBuilder();
            for (int row = 0; row < SIZE; ++ row) {
                for (int col = 0; col < SIZE; ++ col) {
                    b.append(cells[getPos(row, col)] ? '#' : ' ');
                }
                b.append('\n');
            }
            return b.toString();
        }
    }

    public static void main(String[] args) throws Exception {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("solutions_b64.txt"));
                BufferedReader reader = new BufferedReader(new FileReader("nonograms_b64.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(new Nonogram(line).minimize().toBase64() + "\n");
                //writer.write(new Nonogram(line).maximize().toBase64() + "\n");
            }
        }
        System.out.printf("%d -> %d", sourceBlack, targetBlack);
    }
}
Sleafar
fuente
1

Bash - 10,239,288 cuadrados

Como solución de referencia de último lugar:

cp nonograms_b64.txt solutions_b64.txt

Dado que su programa puede devolver la misma cuadrícula si no puede encontrar una solución mejor, imprimir el archivo completo literalmente también es válido.

Hay un total de 10,239,288 cuadrados negros en el archivo de prueba, que está bastante cerca de los 10,240,000 que esperaría del 80% de los cuadrados completados de 50,000 cuadrículas con 256 cuadrados cada uno. Como de costumbre con mis preguntas sobre la batería de la prueba, he seleccionado la cantidad de casos de prueba con la expectativa de que los puntajes óptimos estarán alrededor del rango de 2 millones, aunque sospecho que los puntajes estarán más cerca de 4 o 5 millones esta vez. .


Si alguien puede crear una solución que maximice los cuadrados negros en lugar de minimizarlos y logre obtener más de 10,240,000, podría considerar darle una recompensa.

Joe Z.
fuente
1

Matlab, 7,270,894 cuadrados (~ 71% del original)

La idea es una búsqueda codiciosa simple y repetida: para cada cuadrado negro, intente si puede establecerlo en blanco sin cambiar las magnitudes no gráficas. Repite esto dos veces. (Podrías lograr mejores resultados con más repeticiones, pero no de forma gratuita: da como resultado un tiempo de ejecución correspondientemente más largo. Ahora fueron unos 80 minutos. Lo haría, si no tuviéramos que calcular todas las 50k cajas de prueba ...)

Aquí el código (cada función en un archivo separado, como de costumbre).

function D = b64decode(E)
% accepts a string of base 64 encoded data, and returns a array of zeros
% and ones
F = E;
assert( mod(numel(E),4)==0 && 0 <= sum(E=='=') && sum(E=='=') <= 2,'flawed base 64 code')

F('A' <= E & E<= 'Z') = F('A' <= E & E<= 'Z') -'A';       %upper case
F('a' <= E & E<= 'z') = F('a' <= E & E<= 'z') -'a' + 26;  %lower case
F('0'<= E & E <= '9') = F('0'<= E & E <= '9') -'0' + 52;  %digits
F( E == '+') = 62;
F( E == '/') = 63;
F( E == '=') = 0;

D=zeros(1,numel(E)*3*8/4);

for k=1:numel(E);
    D(6*(k-1)+1 + (0:5)) = dec2bin(F(k),6)-'0';
end

if E(end) == '=';
    D(end-7:end) = [];
    if E(end-1) == '=';
        D(end-7:end) = [];
    end
end
end

function E = b64encode(D)
assert(mod(numel(D),8)==0,'flawed byte code');
N=0;
while mod(numel(D),6) ~= 0;
    D = [D,zeros(1,8)];
    N = N+1;
end
dict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

E=zeros(1,numel(D)/6)+'=';
for k=0:numel(E)-N-1;
    E(k+1) = dict(bin2dec( [D(6*k+(1:6))+'0',''] ) + 1);
end

E = [E,''];
end


function [B,T,O] = reduce_greedy(N)
% reduce greedily
NM = nomographic_magnitude(N);
B = N; %current best
M = N;
T = nnz(N); %current number of filled squares
O = T;

for r=1:2;  %how many repetitions
    I = find(B);
    for k=1:numel(I);
        M = B;
        M( I(k) ) = 0;
        %check whether we have a valid solution
        if all(NM == nomographic_magnitude(M))
            if T > nnz(M); %did we actually reduce the number of filled squares?
                B = M;
                T = nnz(M);
            end
        end
    end
end


%% main file
string_in = fileread('nonograms_b64.txt');
string_out = string_in;
tic
total_new = 0;  %total number of black squares
total_old = 0;
M = 50000;
for k=1:M;
    disp(k/M); %display the progress
    line = string_in(45*(k-1)+(1:44));
    decoded = b64decode(line);        
    nonogram = reshape(decoded,16,[]) ;%store nonogram as a matrix
    [B,T,O] = reduce_greedy(nonogram);
    disp([nnz(B),nnz(nonogram)])
    total_new = total_new + T;
    total_old = total_old + O;
    string_in(45*(k-1)+(1:44)) = b64encode(B(:).');
end
toc
F = fopen('nonograms_b64_out.txt','w');
fprintf(F,string_out);
%%
disp([total_new,total_old])
falla
fuente