Costruisci un Nonographic Magnitude Optimizer ™


12

Un nonogramma è un puzzle game giapponese in cui l'obiettivo è quello di disegnare un'immagine in bianco e nero secondo un elenco di regioni contigue, in questo modo:

Un nonogramma di esempio con una "lambda".

Definire la grandezza non geografica di una riga o colonna in modo che corrisponda al numero di regioni nere contigue in quella riga o colonna. Ad esempio, la riga superiore ha una magnitudine non geografica di 1, poiché in quella riga è presente una regione di 2 quadrati. L'ottava riga ha una magnitudine non geografica di 3 perché ha 2, 2, 1.

Una riga o colonna vuota ha una grandezza non geografica di 0.


Il tuo compito è di scrivere un programma che prende una griglia di soluzione per un nonogramma e produce una griglia di soluzione con il minor numero possibile di quadrati riempiti in cui ogni riga e colonna ha la stessa magnutide non grafica della griglia di soluzione fornita.

Ad esempio, una griglia nonogramma con tutti i quadrati riempiti ha una magnitudine non geografica di 1 su ogni riga o colonna:

Un nonogramma 10x10 in cui viene riempito ogni quadrato.

La stessa grandezza non geografica potrebbe essere ottenuta semplicemente avendo una striscia diagonale attraverso la griglia, riducendo drasticamente il numero di quadrati riempiti:

Un nonogramma 10x10 con la stessa grandezza non geografica del precedente.


Il tuo programma riceverà un input composto da 50.000 righe da questo file ( file di testo tar.gz da 1,32 MB; 2,15 MB decompresso), ognuno dei quali rappresenta una singola griglia di soluzione nonogramma 16 × 16 con quadrati riempiti casualmente (80% nero) e produrre altre 50.000 righe, ognuna contenente la griglia della soluzione ottimizzata per la griglia di input corrispondente.

Ogni griglia è rappresentata come una stringa base64 con 43 caratteri (codifica dei quadrati da sinistra a destra, quindi dall'alto verso il basso) e il programma dovrà restituire il proprio output nello stesso formato. Ad esempio, la prima griglia nel file è E/lu/+7/f/3rp//f799xn/9//2mv//nvj/bt/yc9/40=e viene visualizzata come:

primo esempio nonogramma

La griglia inizia con Ecui mappare 000100, quindi le prime sei celle nella riga superiore sono tutte bianche tranne la quarta. Il personaggio successivo è /quale mappa 111111, quindi le 6 celle successive sono tutte nere - e così via.


Il programma deve effettivamente restituire una griglia di soluzione con le dimensioni non geografiche corrette per ciascuno dei 50.000 casi di test. È consentito restituire la stessa griglia dell'input se non è stato trovato nulla di meglio.

Il programma per restituire il minor numero di quadrati riempiti (in qualsiasi lingua) è il vincitore, con il codice più corto è il tiebreaker.


Quadro di valutazione attuale:

  1. 3.637.260 - Sleafar, Java
  2. 7.270.894 - flawr, Matlab
  3. 10.239.288 - Joe Z., Bash

1
Non vedo davvero il punto della codifica base 64 e lavorare con questo è un vero dolore. Non sarebbe più semplice creare linee di uno e zero? O codificare il tutto come bitmap?
Flawr,

@flawr: riduce le dimensioni del file, principalmente (di un fattore 6 rispetto a solo 1 e 0). Inoltre, le bitmap sarebbero ancora più difficili da lavorare.
Joe Z.

Potresti semplicemente creare un'immagine in bianco e nero, facile da leggere / scrivere e le stesse dimensioni della codifica b64.
flawr

2
inoltre non è un fan della codifica b64, per input e / o output. perché non lasciare che l'I / O sia in qualsiasi formato conveniente?
Sparr,

1
Supponendo che lo sia, dovrei avere una soluzione ottimale entro domani.
Quintopia,

Risposte:


7

Python 2 e PuLP - 2.644.688 quadrati (ottimizzati in modo ottimale); 10.753.553 quadrati (ottimizzati in modo ottimale)

Giocato a mini golf a 1152 byte

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

(NB: le linee fortemente rientrate iniziano con tabulazioni, non spazi.)

Esempio di output: https://drive.google.com/file/d/0B-0NVE9E8UJiX3IyQkJZVk82Vkk/view?usp=sharing

Si scopre che problemi come quelli sono prontamente convertibili in Integer Linear Program, e avevo bisogno di un problema di base per imparare a usare PuLP - un'interfaccia Python per una varietà di solutori di LP - per un mio progetto. Si scopre anche che PuLP è estremamente facile da usare e il costruttore di LP non golfato ha funzionato perfettamente la prima volta che l'ho provato.

Le due cose belle sull'impiego di un risolutore di indirizzi IP branch-and-bound per fare il duro lavoro di risolverlo per me (oltre a non dover implementare un risolutore di branch e bound) sono che

  • I solutori appositamente progettati sono molto veloci. Questo programma risolve tutti i 50000 problemi in circa 17 ore sul mio PC di casa relativamente di fascia bassa. Per risolvere ogni istanza sono occorsi da 1-1,5 secondi.
  • Producono soluzioni ottimali garantite (o ti dicono che non sono riuscite a farlo). Quindi, posso essere fiducioso che nessuno batterà il mio punteggio in quadrati (anche se qualcuno potrebbe legarlo e battermi sulla parte del golf).

Come usare questo programma

Innanzitutto, dovrai installare PuLP. pip install pulpdovrebbe fare il trucco se hai installato pip.

Quindi, dovrai inserire quanto segue in un file chiamato "c": https://drive.google.com/file/d/0B-0NVE9E8UJiNFdmYlk1aV9aYzQ/view?usp=sharing

Quindi, esegui questo programma in qualsiasi build Python 2 in ritardo dalla stessa directory. In meno di un giorno, avrai un file chiamato "s" che contiene 50.000 griglie di nonogrammi risolte (in formato leggibile), ognuna con il numero totale di quadrati riempiti elencati sotto di essa.

Se invece desideri massimizzare il numero di quadrati riempiti, modifica invece la LpMinimizeriga 8 LpMaximize. Otterrai un output molto simile a questo: https://drive.google.com/file/d/0B-0NVE9E8UJiYjJ2bzlvZ0RXcUU/view?usp=sharing

Formato di input

Questo programma utilizza un formato di input modificato, poiché Joe Z. ha affermato che ci sarebbe permesso di ricodificare il formato di input se ci piace in un commento sull'OP. Fai clic sul link sopra per vedere come appare. Si compone di 10000 righe, ciascuna contenente 16 numeri. Le linee numerate pari sono le magnitudini per le righe di una determinata istanza, mentre le linee dispari sono le magnitudini per le colonne della stessa istanza della linea sopra di esse. Questo file è stato generato dal seguente programma:

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

(Questo programma di ricodifica mi ha anche dato un'ulteriore opportunità per testare la mia classe BitQueue personalizzata che ho creato per lo stesso progetto sopra menzionato. È semplicemente una coda in cui i dati possono essere trasferiti come sequenze di bit O byte e da cui i dati possono essere spuntato un po 'o un byte alla volta. In questo caso, ha funzionato perfettamente.)

Ho ricodificato l'input per il motivo specifico che per costruire un ILP, le informazioni extra sulle griglie utilizzate per generare le magnitudini sono perfettamente inutili. Le magnitudini sono gli unici vincoli, e quindi le magnitudini sono tutto ciò di cui avevo bisogno di accedere.

Costruttore ILP non golfato

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

Questo è il programma che ha effettivamente prodotto l '"output di esempio" collegato sopra. Da qui le stringhe extra lunghe alla fine di ogni griglia, che ho troncato durante il golf. (La versione golfata dovrebbe produrre un output identico, meno le parole "Filled squares for ")

Come funziona

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)

Uso una griglia 18x18, con la parte centrale 16x16 che rappresenta la vera soluzione del puzzle. cellsè questa griglia. La prima riga crea 324 variabili binarie: "cell_0_0", "cell_0_1" e così via. Creo anche griglie degli "spazi" tra e intorno alle celle nella parte della soluzione della griglia. rowsepspunta alle 289 variabili che simboleggiano gli spazi che separano le celle in orizzontale, mentre colsepsallo stesso modo punta alle variabili che segnano gli spazi che separano le celle in verticale. Ecco un diagramma 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

I 0s e s sono i valori binari rilevati dalle cellvariabili, i |s sono i valori binari tracciati dalle rowsepvariabili, e le -s sono i valori binari tracciati dalle colsepvariabili.

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

Questa è la funzione oggettiva. Solo la somma di tutte le cellvariabili. Poiché si tratta di variabili binarie, questo è esattamente il numero di quadrati riempiti nella soluzione.

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

Questo porta a zero le celle attorno al bordo esterno della griglia (motivo per cui le ho rappresentate come zero sopra). Questo è il modo più conveniente per tenere traccia di quanti "blocchi" di celle sono riempiti, poiché assicura che ogni cambiamento da non riempito a riempito (spostandosi attraverso una colonna o riga) sia accompagnato da un cambiamento corrispondente da riempito a non riempito (e viceversa ), anche se la prima o l'ultima cella della riga è piena. Questo è l'unico motivo per utilizzare una griglia 18x18 in primo luogo. Non è l'unico modo per contare i blocchi, ma penso che sia il più semplice.

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,""

Questa è la vera carne della logica dell'ILP. Fondamentalmente richiede che ogni cella (diversa da quelle nella prima riga e colonna) sia lo xor logico della cella e del separatore direttamente alla sua sinistra nella sua riga e direttamente sopra di essa nella sua colonna. Ho ottenuto i vincoli che simulano un xor all'interno di un programma intero {0,1} da questa meravigliosa risposta: /cs//a/12118/44289

Per spiegare un po 'di più: questo vincolo xor fa sì che i separatori possano essere 1 se e solo se si trovano tra celle che sono 0 e 1 (segnando un passaggio da non riempito a riempito o viceversa). Pertanto, in una riga o colonna ci saranno esattamente il doppio di separatori a 1 valore rispetto al numero di blocchi in quella riga o colonna. In altre parole, la somma dei separatori su una determinata riga o colonna è esattamente il doppio della grandezza di quella riga / colonna. Da qui i seguenti vincoli:

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,""

E questo è praticamente tutto. Il resto chiede semplicemente al risolutore predefinito di risolvere l'ILP, quindi formatta la soluzione risultante mentre la scrive nel file.


Davvero una buona risposta. Fammi venire voglia di conoscere i solutori di LP. Pensi che potrebbe essere usato per risolvere il puzzle flood it (link) per una scheda 19x19, 6 colori (per quanto riguarda il tempo per calcolare una soluzione)? Ho già risposto a quel concorso (e l'ho vinto), tuttavia il mio metodo (algoritmo di ricerca A *) offre solo soluzioni non ottimali.
Tigrou,

@tigrou Grazie. Non sono sicuro che il problema dell'inondazione sia abbastanza lineare da ammettere una simile soluzione. Certamente non riesco a vedere come modellarlo in questo modo.
Quintopia,

Sembra che qualcuno l'abbia già provato: kunigami.blog/2012/09/16/flood-it-an-exact-approach Tuttavia non sono riuscite a trovare soluzioni ottimali in tempi fattibili per una scheda 14x14.
Tigrou,

3

Java, 6.093.092 4.332.656 3.637.260 quadrati (minimizzati), 10.567.550 10.567.691 10.568.746 quadrati (massimizzati)

Entrambe le varianti del programma eseguono ripetutamente operazioni sulla griglia di origine, senza modificare la grandezza.

Minimizer

ridurre ()

inserisci qui la descrizione dell'immagine

Se un quadrato nero ha 2 vicini bianchi e 2 vicini neri con un angolo di 90 °, può essere sostituito da un quadrato bianco.

MoveLine ()

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

In configurazioni come sopra la linea nera può essere spostata a destra. Questo viene fatto ripetutamente per tutte e 4 le direzioni della linea in senso orario e antiorario, per aprire nuove possibilità di restringimento.

Maximizer

Rimuovi il commento dalla riga main()e commenta la riga sopra per questa versione.

crescere()

inserisci qui la descrizione dell'immagine

Se un quadrato bianco ha 2 vicini bianchi e 2 vicini neri con un angolo di 90 °, può essere sostituito da un quadrato nero.

MoveLine ()

Come in Minimizer.

fonte

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

1

Bash - 10.239.288 quadrati

Come soluzione di riferimento all'ultimo posto:

cp nonograms_b64.txt solutions_b64.txt

Dal momento che il tuo programma è autorizzato a restituire la stessa griglia se non riesce a trovare una soluzione migliore, anche la stampa integrale del file è valida.

Ci sono un totale di 10.239.288 quadrati neri nel file di test, che è abbastanza vicino ai 10.240.000 che ti aspetteresti dall'80% dei quadrati riempiti su 50.000 griglie con 256 quadrati ciascuno. Come al solito con le mie domande sulla batteria di test, ho selezionato il numero di casi di test con l'aspettativa che i punteggi ottimali si aggirino attorno all'intervallo di 2 milioni, anche se sospetto che i punteggi saranno più vicini a 4 o 5 milioni questa volta .


Se qualcuno è in grado di creare una soluzione che massimizzi i quadrati neri anziché minimizzarli e riesca a superare i 10.240.000, potrei prendere in considerazione l'idea di dargli una taglia.


1

Matlab, 7.270.894 quadrati (~ 71% dell'originale)

L'idea è una ricerca avida ripetuta semplice: per ogni quadrato nero, prova se puoi impostarlo su bianco senza cambiare le magnitudini non geografiche. Ripeti due volte. (Potresti ottenere risultati migliori con più ripetizioni, ma non gratuitamente: si traduce in un tempo di funzionamento corrispondente più lungo. Ora sono stati circa 80 minuti. Lo farei, se non dovessimo calcolare tutti i 50k testcase ...)

Qui il codice (ogni funzione in un file separato, come al solito).

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])
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.