Criar uma IA de pintura de inundação


34

No jogo Flood Paint, o objetivo do jogo é fazer com que todo o tabuleiro fique da mesma cor no menor número de turnos possível.

O jogo começa com um quadro que se parece com isso:

3 3 5 4 1 3 4 1 5
5 1 3 4 1 1 5 2 1
6 5 2 3 4 3 3 4 3
4 4 4 5 5 5 4 1 4
6 2 5 3[3]1 1 6 6
5 5 1 2 5 2 6 6 3
6 1 1 5 3 6 2 3 6
1 2 2 4 5 3 5 1 2
3 6 6 1 5 1 3 2 4

Atualmente, o número (representando uma cor) no centro do tabuleiro é 3. A cada turno, o quadrado no centro muda de cor e todos os quadrados da mesma cor que são alcançáveis ​​a partir do centro, movendo-se horizontal ou verticalmente ( ou seja, na região de inundação da praça central) mudará de cor com ela. Portanto, se o quadrado central mudar de cor para 5:

3 3 5 4 1 3 4 1 5
5 1 3 4 1 1 5 2 1
6 5 2 3 4 3 3 4 3
4 4 4 5 5 5 4 1 4
6 2 5 5[5]1 1 6 6
5 5 1 2 5 2 6 6 3
6 1 1 5 3 6 2 3 6
1 2 2 4 5 3 5 1 2
3 6 6 1 5 1 3 2 4

então o 3 que estava à esquerda do centro 3 também mudará de cor. Agora há um total de sete 5 alcançáveis ​​a partir do centro e, portanto, se mudarmos de cor para 4:

3 3 5 4 1 3 4 1 5
5 1 3 4 1 1 5 2 1
6 5 2 3 4 3 3 4 3
4 4 4 4 4 4 4 1 4
6 2 4 4[4]1 1 6 6
5 5 1 2 4 2 6 6 3
6 1 1 5 3 6 2 3 6
1 2 2 4 5 3 5 1 2
3 6 6 1 5 1 3 2 4

a região pintada aumenta novamente de tamanho dramaticamente.

Sua tarefa é criar um programa que use uma grade de 19 por 19 cores de 1 a 6 como entrada, na forma que você escolher:

4 5 1 1 2 2 1 6 2 6 3 4 2 3 2 3 1 6 3
4 2 6 3 4 4 5 6 4 4 5 3 3 3 3 5 4 3 4
2 3 5 2 2 5 5 1 2 6 2 6 6 2 1 6 6 1 2
4 6 5 5 5 5 4 1 6 6 3 2 6 4 2 6 3 6 6
1 6 4 4 4 4 6 4 2 5 5 3 2 2 4 1 5 2 5
1 6 2 1 5 1 6 4 4 1 5 1 3 4 5 2 3 4 1
3 3 5 3 2 2 2 4 2 1 6 6 6 6 1 4 5 2 5
1 6 1 3 2 4 1 3 3 4 6 5 1 5 5 3 4 3 3
4 4 1 5 5 1 4 6 3 3 4 5 5 6 1 6 2 6 4
1 4 2 5 6 5 5 3 2 5 5 5 3 6 1 4 4 6 6
4 6 6 2 6 6 2 4 2 6 1 5 6 2 3 3 4 3 6
6 1 3 6 3 5 5 3 6 1 3 4 4 5 1 2 6 4 3
2 6 1 3 2 4 2 6 1 1 5 2 6 6 6 6 3 3 3
3 4 5 4 6 6 3 3 4 1 1 6 4 5 1 3 4 1 2
4 2 6 4 1 5 3 6 4 3 4 5 4 2 1 1 4 1 1
4 2 4 1 5 2 2 3 6 6 6 5 2 5 4 5 4 5 1
5 6 2 3 4 6 5 4 1 3 2 3 2 1 3 6 2 2 4
6 5 4 1 3 2 2 1 1 1 6 1 2 6 2 5 6 4 5
5 1 1 4 2 6 2 5 6 1 3 3 4 1 6 1 2 1 2

e retorne uma sequência de cores que o quadrado central mudará a cada turno, novamente no formato de sua escolha:

263142421236425431645152623645465646213545631465

No final de cada sequência de movimentos, os quadrados na grade 19 por 19 devem ter a mesma cor.

Seu programa deve ser totalmente determinístico; Soluções pseudo-aleatórias são permitidas, mas o programa deve gerar sempre a mesma saída para o mesmo caso de teste.

O programa vencedor executará o menor número total de etapas para resolver todos os 100.000 casos de teste encontrados neste arquivo (arquivo de texto compactado, 14,23 MB). Se duas soluções seguirem o mesmo número de etapas (por exemplo, se ambas encontraram a estratégia ideal), o programa mais curto vencerá.


BurntPizza escreveu um programa em Java para verificar os resultados do teste. Para usar este programa, execute sua submissão e canalize a saída para um arquivo chamado steps.txt. Em seguida, execute este programa com steps.txte o floodtestarquivo no mesmo diretório. Se sua entrada for válida e produzir soluções corretas para todos os arquivos, ela deverá passar em todos os testes e retornarAll boards solved successfully.

import java.io.*;
import java.util.*;

public class PainterVerifier {

    public static void main(String[] args) throws FileNotFoundException {

        char[] board = new char[361];

        Scanner s = new Scanner(new File("steps.txt"));
        Scanner b = new Scanner(new File("floodtest"));

        int lineNum = 0;

        caseloop: while (b.hasNextLine()) {

            for (int l = 0; l < 19; l++) {
                String lineb = b.nextLine();
                if (lineb.isEmpty())
                    continue caseloop;
                System.arraycopy(lineb.toCharArray(), 0, board, l * 19, 19);
            }

            String line = s.nextLine();
            if (line.isEmpty())
                continue;
            char[] steps = line.toCharArray();

            Stack<Integer> nodes = new Stack<Integer>();

            for (char c : steps) {
                char targetColor = board[180];
                char replacementColor = c;

                nodes.push(180);

                while (!nodes.empty()) {
                    int n = nodes.pop();
                    if (n < 0 || n > 360)
                        continue;
                    if (board[n] == targetColor) {
                        board[n] = replacementColor;
                        if (n % 19 > 0)
                            nodes.push(n - 1);
                        if (n % 19 < 18)
                            nodes.push(n + 1);
                        if (n / 19 > 0)
                            nodes.push(n - 19);
                        if (n / 19 < 18)
                            nodes.push(n + 19);
                    }
                }
            }
            char center = board[180];
            for (char c : board)
                if (c != center) {
                    s.close();
                    b.close();

                    System.out.println("\nIncomplete board found!\n\tOn line " + lineNum + " of steps.txt");
                    System.exit(0);
                }

            if (lineNum % 5000 == 0)
                System.out.printf("Verification %d%c complete...\n", lineNum * 100 / 100000, '%');

            lineNum++;
        }
        s.close();
        b.close();
        System.out.println("All boards solved successfully.");
    }
}

Além disso, um placar, já que os resultados não são realmente classificados por pontuação e aqui isso realmente importa muito:

  1. 1.985.078 - smack42, Java
  2. 2.075.452 - usuário1502040, C
  3. 2.098.382 - tigrou, C #
  4. 2.155.834 - CoderTao, C #
  5. 2.201.995 - MrBackend, Java
  6. 2.383.569 - CoderTao, C #
  7. 2.384.020 - Herjan, C
  8. 2.403.189 - Origineil, Java
  9. 2.445.761 - Herjan, C
  10. 2.475.056 - Jeremy List, Haskell
  11. 2.480.714 - SteelTermite, C (2.395 bytes)
  12. 2.480.714 - Herjan, Java (4.702 bytes)
  13. 2.588.847 - BurntPizza, Java (2.748 bytes)
  14. 2.588.847 - Gero3, node.js (4.641 bytes)
  15. 2.979.145 - Teun Pronk, Delphi XE3
  16. 4.780.841 - BurntPizza, Java
  17. 10.800.000 - Joe Z., Python

2
A julgar por sua própria submissão, a saída não deve conter espaços?
Martin Ender

5
Vale ressaltar que os dados de entrada do teste não possuem espaços entre os números.
Ndscore

3
Você ainda pode escrever. Se prejudicar o atual vencedor, alterarei a resposta aceita.
Joe Z.

4
A restrição de tempo é "ela precisa ser rápida o suficiente para você executá-la e postar os resultados reais aqui".
Joe Z.

2
@AlexanderRevo Eu pensei que não movi o arquivo, mas aparentemente o link foi ativado e alterado sem que eu o fizesse. Aqui está o link novamente.
Joe Z.

Respostas:


4

Java - 1.985.078 etapas

https://github.com/smack42/ColorFill

Outra entrada tardia. O arquivo de resultados que contém as 1.985.078 etapas pode ser encontrado aqui .

Algumas informações básicas:

Eu descobri esse desafio alguns anos atrás, quando comecei a programar meu próprio clone do jogo Flood-it.

Algoritmo DFS e A * "melhor do que incompleto"
Desde o início, eu queria criar um bom algoritmo de resolução para este jogo. Com o tempo, pude melhorar meu solucionador incluindo várias estratégias que faziam pesquisas incompletas diferentes (semelhantes às usadas nos outros programas aqui) e usando o melhor resultado dessas estratégias para cada solução. Até reimplementei o algoritmo A * do tigrou em Java e o adicionei ao meu solucionador para obter soluções melhores em geral do que o resultado do tigrou.

exaustivo algoritmo DFS
Então me concentrei em um algoritmo que sempre encontra as soluções ideais. Eu gastei muito esforço para otimizar minha estratégia exaustiva de busca em profundidade. Para acelerar a pesquisa, incluí um mapa de hash que armazena todos os estados explorados, para que a pesquisa possa evitá-los novamente. Embora esse algoritmo funcione bem e resolva todos os quebra-cabeças de 14x14 com rapidez suficiente, ele usa muita memória e corre muito lentamente com os quebra-cabeças de 19x19 neste desafio de código.

Algoritmo Puchert A *
Há alguns meses, fui contactado por Aaron e Simon Puchert para resolver o problema do Flood-It . Esse programa usa um algoritmo do tipo A * com uma heurística admissível (em contraste com a de tigrou) e move a poda de maneira semelhante à pesquisa por ponto de salto. Percebi rapidamente que este programa é muito rápido e encontra as soluções ideais !

Obviamente, tive que reimplementar esse ótimo algoritmo e adicioná-lo ao meu programa. Fiz um esforço para otimizar meu programa Java para rodar tão rápido quanto o programa C ++ original dos irmãos Puchert. Então decidi tentar os 100.000 casos de teste desse desafio. Na minha máquina, o programa funcionou por mais de 120 horas para encontrar as etapas de 1.985.078, usando minha implementação do algoritmo Puchert A * .

Essa deve ser a melhor solução possível para esse desafio, a menos que haja alguns erros no programa que resultem em soluções abaixo do ideal. Qualquer feedback é bem-vindo!

edit: adicionou partes relevantes do código aqui:

classe AStarPuchertStrategy

/**
 * a specific strategy for the AStar (A*) solver.
 * <p>
 * the idea is taken from the program "floodit" by Aaron and Simon Puchert,
 * which can be found at <a>https://github.com/aaronpuchert/floodit</a>
 */
public class AStarPuchertStrategy implements AStarStrategy {

    private final Board board;
    private final ColorAreaSet visited;
    private ColorAreaSet current, next;
    private final short[] numCaNotFilledInitial;
    private final short[] numCaNotFilled;

    public AStarPuchertStrategy(final Board board) {
        this.board = board;
        this.visited = new ColorAreaSet(board);
        this.current = new ColorAreaSet(board);
        this.next = new ColorAreaSet(board);
        this.numCaNotFilledInitial = new short[board.getNumColors()];
        for (final ColorArea ca : board.getColorAreasArray()) {
            ++this.numCaNotFilledInitial[ca.getColor()];
        }
        this.numCaNotFilled = new short[board.getNumColors()];
    }

    /* (non-Javadoc)
     * @see colorfill.solver.AStarStrategy#setEstimatedCost(colorfill.solver.AStarNode)
     */
    @Override
    public void setEstimatedCost(final AStarNode node) {

        // quote from floodit.cpp: int State::computeValuation()
        // (in branch "performance")
        //
        // We compute an admissible heuristic recursively: If there are no nodes
        // left, return 0. Furthermore, if a color can be eliminated in one move
        // from the current position, that move is an optimal move and we can
        // simply use it. Otherwise, all moves fill a subset of the neighbors of
        // the filled nodes. Thus, filling that layer gets us at least one step
        // closer to the end.

        node.copyFloodedTo(this.visited);
        System.arraycopy(this.numCaNotFilledInitial, 0, this.numCaNotFilled, 0, this.numCaNotFilledInitial.length);
        {
            final ColorAreaSet.FastIteratorColorAreaId iter = this.visited.fastIteratorColorAreaId();
            int nextId;
            while ((nextId = iter.nextOrNegative()) >= 0) {
                --this.numCaNotFilled[this.board.getColor4Id(nextId)];
            }
        }

        // visit the first layer of neighbors, which is never empty, i.e. the puzzle is not solved yet
        node.copyNeighborsTo(this.current);
        this.visited.addAll(this.current);
        int completedColors = 0;
        {
            final ColorAreaSet.FastIteratorColorAreaId iter = this.current.fastIteratorColorAreaId();
            int nextId;
            while ((nextId = iter.nextOrNegative()) >= 0) {
                final byte nextColor = this.board.getColor4Id(nextId);
                if (--this.numCaNotFilled[nextColor] == 0) {
                    completedColors |= 1 << nextColor;
                }
            }
        }
        int distance = 1;

        while(!this.current.isEmpty()) {
            this.next.clear();
            final ColorAreaSet.FastIteratorColorAreaId iter = this.current.fastIteratorColorAreaId();
            int thisCaId;
            if (0 != completedColors) {
                // We can eliminate colors. Do just that.
                // We also combine all these elimination moves.
                distance += Integer.bitCount(completedColors);
                final int prevCompletedColors = completedColors;
                completedColors = 0;
                while ((thisCaId = iter.nextOrNegative()) >= 0) {
                    final ColorArea thisCa = this.board.getColorArea4Id(thisCaId);
                    if ((prevCompletedColors & (1 << thisCa.getColor())) != 0) {
                        // completed color
                        // expandNode()
                        for (final int nextCaId : thisCa.getNeighborsIdArray()) {
                            if (!this.visited.contains(nextCaId)) {
                                this.visited.add(nextCaId);
                                this.next.add(nextCaId);
                                final byte nextColor = this.board.getColor4Id(nextCaId);
                                if (--this.numCaNotFilled[nextColor] == 0) {
                                    completedColors |= 1 << nextColor;
                                }
                            }
                        }
                    } else {
                        // non-completed color
                        // move node to next layer
                        this.next.add(thisCaId);
                    }
                }
            } else {
                // Nothing found, do the color-blind pseudo-move
                // Expand current layer of nodes.
                ++distance;
                while ((thisCaId = iter.nextOrNegative()) >= 0) {
                    final ColorArea thisCa = this.board.getColorArea4Id(thisCaId);
                    // expandNode()
                    for (final int nextCaId : thisCa.getNeighborsIdArray()) {
                        if (!this.visited.contains(nextCaId)) {
                            this.visited.add(nextCaId);
                            this.next.add(nextCaId);
                            final byte nextColor = this.board.getColor4Id(nextCaId);
                            if (--this.numCaNotFilled[nextColor] == 0) {
                                completedColors |= 1 << nextColor;
                            }
                        }
                    }
                }
            }

            // Move the next layer into the current.
            final ColorAreaSet tmp = this.current;
            this.current = this.next;
            this.next = tmp;
        }
        node.setEstimatedCost(node.getSolutionSize() + distance);
    }

}

parte da classe AStarSolver

private void executeInternalPuchert(final ColorArea startCa) throws InterruptedException {
    final Queue<AStarNode> open = new PriorityQueue<AStarNode>(AStarNode.strongerComparator());
    open.offer(new AStarNode(this.board, startCa));
    AStarNode recycleNode = null;
    while (open.size() > 0) {
        if (Thread.interrupted()) { throw new InterruptedException(); }
        final AStarNode currentNode = open.poll();
        if (currentNode.isSolved()) {
            this.addSolution(currentNode.getSolution());
            return;
        } else {
            // play all possible colors
            int nextColors = currentNode.getNeighborColors(this.board);
            while (0 != nextColors) {
                final int l1b = nextColors & -nextColors; // Integer.lowestOneBit()
                final int clz = Integer.numberOfLeadingZeros(l1b); // hopefully an intrinsic function using instruction BSR / LZCNT / CLZ
                nextColors ^= l1b; // clear lowest one bit
                final byte color = (byte)(31 - clz);
                final AStarNode nextNode = currentNode.copyAndPlay(color, recycleNode, this.board);
                if (null != nextNode) {
                    recycleNode = null;
                    this.strategy.setEstimatedCost(nextNode);
                    open.offer(nextNode);
                }
            }
        }
        recycleNode = currentNode;
    }
}

parte da classe AStarNode

/**
 * check if this color can be played. (avoid duplicate moves)
 * the idea is taken from the program "floodit" by Aaron and Simon Puchert,
 * which can be found at <a>https://github.com/aaronpuchert/floodit</a>
 * @param nextColor
 * @return
 */
private boolean canPlay(final byte nextColor, final List<ColorArea> nextColorNeighbors) {
    final byte currColor = this.solution[this.solutionSize];
    // did the previous move add any new "nextColor" neighbors?
    boolean newNext = false;
next:   for (final ColorArea nextColorNeighbor : nextColorNeighbors) {
        for (final ColorArea prevNeighbor : nextColorNeighbor.getNeighborsArray()) {
            if ((prevNeighbor.getColor() != currColor) && this.flooded.contains(prevNeighbor)) {
                continue next;
            }
        }
        newNext = true;
        break next;
    }
    if (!newNext) {
        if (nextColor < currColor) {
            return false;
        } else {
            // should nextColor have been played before currColor?
            for (final ColorArea nextColorNeighbor : nextColorNeighbors) {
                for (final ColorArea prevNeighbor : nextColorNeighbor.getNeighborsArray()) {
                    if ((prevNeighbor.getColor() == currColor) && !this.flooded.contains(prevNeighbor)) {
                        return false;
                    }
                }
            }
            return true;
        }
    } else {
        return true;
    }
}

/**
 * try to re-use the given node or create a new one
 * and then play the given color in the result node.
 * @param nextColor
 * @param recycleNode
 * @return
 */
public AStarNode copyAndPlay(final byte nextColor, final AStarNode recycleNode, final Board board) {
    final List<ColorArea> nextColorNeighbors = new ArrayList<ColorArea>(128);  // constant, arbitrary initial capacity
    final ColorAreaSet.FastIteratorColorAreaId iter = this.neighbors.fastIteratorColorAreaId();
    int nextId;
    while ((nextId = iter.nextOrNegative()) >= 0) {
        final ColorArea nextColorNeighbor = board.getColorArea4Id(nextId);
        if (nextColorNeighbor.getColor() == nextColor) {
            nextColorNeighbors.add(nextColorNeighbor);
        }
    }
    if (!this.canPlay(nextColor, nextColorNeighbors)) {
        return null;
    } else {
        final AStarNode result;
        if (null == recycleNode) {
            result = new AStarNode(this);
        } else {
            // copy - compare copy constructor
            result = recycleNode;
            result.flooded.copyFrom(this.flooded);
            result.neighbors.copyFrom(this.neighbors);
            System.arraycopy(this.solution, 0, result.solution, 0, this.solutionSize + 1);
            result.solutionSize = this.solutionSize;
            //result.estimatedCost = this.estimatedCost;  // not necessary to copy
        }
        // play - compare method play()
        for (final ColorArea nextColorNeighbor : nextColorNeighbors) {
            result.flooded.add(nextColorNeighbor);
            result.neighbors.addAll(nextColorNeighbor.getNeighborsIdArray());
        }
        result.neighbors.removeAll(result.flooded);
        result.solution[++result.solutionSize] = nextColor;
        return result;
    }
}

2
Bem-vindo ao PPCG! Você poderia incluir o código relevante para o solucionador na própria resposta, para que seja independente, caso o seu repositório do github se mova ou desça?
Martin Ender

Adicionamos as partes mais relevantes do código aqui: minha implementação do "algoritmo Puchert A *". (este trecho de código não é auto-suficiente e não pode ser compilado como está)
smack42

Estou feliz que alguém tenha encontrado uma solução perfeita / ideal para isso. Mas, por outro lado, significa que não haverá mais concorrência / novas respostas.
tigrou

15

C # - 2.098.382 etapas

Eu tento muitas coisas, a maioria delas falha e simplesmente não funcionou até recentemente. Eu tenho algo interessante o suficiente para postar uma resposta.

Certamente, existem maneiras de melhorar isso ainda mais. Acho que é possível seguir os passos de 2 milhões.

Demorou aproximadamente 7 hourspara gerar resultados. Aqui está um arquivo txt com todas as soluções, caso alguém queira vê-las: results.zip

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

namespace FloodPaintAI
{
    class Node
    {   
        public byte Value;             //1-6
        public int Index;              //unique identifier, used for easily deepcopying the graph
        public List<Node> Neighbours;  
        public List<Tuple<int, int>> NeighboursPositions; //used by BuildGraph() 

        public int Depth;    //used by GetSumDistances() 
        public bool Checked; // 

        public Node(byte value, int index)
        {
            Value = value;      
            Index = index;          
        }

        public Node(Node node)
        {           
            Value = node.Value; 
            Index = node.Index;                     
        }
    }

    class Board
    {
        private const int SIZE = 19;
        private const int STARTPOSITION = 9;

        public Node Root;         //root of graph. each node is a set of contiguous, same color square
        public List<Node> Nodes;  //all nodes in the graph, used for deep copying


        public int EstimatedCost; //estimated cost, used by A* Pathfinding
        public List<byte> Solution;

        public Board(StreamReader input)
        {                   
            byte[,] board = new byte[SIZE, SIZE];
            for(int j = 0 ; j < SIZE ; j++)
            {
                string line = input.ReadLine();
                for(int i = 0 ; i < SIZE ; i++)         
                {                                       
                    board[j, i] = byte.Parse(line[i].ToString());
                }               
            }
            Solution = new List<byte>();
            BuildGraph(board);  
        }

        public Board(Board boardToCopy)
        {               
            //copy the graph            
            Nodes = new List<Node>(boardToCopy.Nodes.Count);
            foreach(Node nodeToCopy in boardToCopy.Nodes)
            {
                Node node = new Node(nodeToCopy);
                Nodes.Add(node);
            }

            //copy "Neighbours" property
            for(int i = 0 ; i < boardToCopy.Nodes.Count ; i++)
            {
                Node node = Nodes[i];
                Node nodeToCopy = boardToCopy.Nodes[i];

                node.Neighbours = new List<Node>(nodeToCopy.Neighbours.Count);
                foreach(Node neighbour in nodeToCopy.Neighbours)
                {
                    node.Neighbours.Add(Nodes[neighbour.Index]);
                }
            }

            Root = Nodes[boardToCopy.Root.Index];
            EstimatedCost = boardToCopy.EstimatedCost;          
            Solution = new List<byte>(boardToCopy.Solution);            
        }

        private void BuildGraph(byte[,] board)
        {                       
            int[,] nodeIndexes = new int[SIZE, SIZE];
            Nodes = new List<Node>();

            //check how much sets we have (1st pass)
            for(int j = 0 ; j < SIZE ; j++)
            {
                for(int i = 0 ; i < SIZE ; i++)         
                {               
                    if(nodeIndexes[j, i] == 0) //not already visited                    
                    {
                        Node newNode = new Node(board[j, i], Nodes.Count);                      
                        newNode.NeighboursPositions = new List<Tuple<int, int>>();
                        Nodes.Add(newNode);

                        BuildGraphFloodFill(board, nodeIndexes, newNode, i, j, board[j, i]);
                    }
                }       
            }

            //set neighbours and root (2nd pass)
            foreach(Node node in Nodes)
            {
                node.Neighbours = new List<Node>();
                node.Neighbours.AddRange(node.NeighboursPositions.Select(x => nodeIndexes[x.Item2, x.Item1]).Distinct().Select(x => Nodes[x - 1]));
                node.NeighboursPositions = null;                
            }
            Root = Nodes[nodeIndexes[STARTPOSITION, STARTPOSITION] - 1];            
        }

        private void BuildGraphFloodFill(byte[,] board, int[,] nodeIndexes, Node node, int startx, int starty, byte floodvalue)
        {
            Queue<Tuple<int, int>> queue = new Queue<Tuple<int, int>>();
            queue.Enqueue(new Tuple<int, int>(startx, starty));

            while(queue.Count > 0)
            {
                Tuple<int, int> position = queue.Dequeue();
                int x = position.Item1;
                int y = position.Item2;

                if(x >= 0 && x < SIZE && y >= 0 && y < SIZE)
                {
                    if(nodeIndexes[y, x] == 0 && board[y, x] == floodvalue)
                    {
                        nodeIndexes[y, x] = node.Index + 1;

                        queue.Enqueue(new Tuple<int, int>(x + 1, y));
                        queue.Enqueue(new Tuple<int, int>(x - 1, y));
                        queue.Enqueue(new Tuple<int, int>(x, y + 1));
                        queue.Enqueue(new Tuple<int, int>(x, y - 1));                                           
                    }               
                    if(board[y, x] != floodvalue)
                        node.NeighboursPositions.Add(position);                         
                }       
            }
        }

        public int GetEstimatedCost()
        {       
            Board current = this;

            //copy current board and play the best color until the end.
            //number of moves required to go the end is the heuristic
            //estimated cost = current cost + heuristic
            while(!current.IsSolved())
            {
                int minSumDistance = int.MaxValue;
                Board minBoard = null;

                //find color which give the minimum sum of distance from root to each other node
                foreach(byte i in current.Root.Neighbours.Select(x => x.Value).Distinct())
                {
                    Board copy = new Board(current);
                    copy.Play(i);                   

                    int distance = copy.GetSumDistances();                  

                    if(distance < minSumDistance)
                    {
                        minSumDistance = distance;
                        minBoard = copy;
                    }
                }
                current = minBoard;
            }           
            return current.Solution.Count;
        }

        public int GetSumDistances()
        {
            //get sum of distances from root to each other node, using BFS
            int sumDistances = 0;           

            //reset marker
            foreach(Node n in Nodes)
            {
                n.Checked = false;                                  
            }

            Queue<Node> queue = new Queue<Node>();
            Root.Checked = true;
            Root.Depth = 0; 
            queue.Enqueue(Root);

            while(queue.Count > 0)
            {
                Node current = queue.Dequeue();                             
                foreach(Node n in current.Neighbours)
                {
                    if(!n.Checked)          
                    {                                   
                        n.Checked = true;                                               
                        n.Depth = current.Depth + 1;
                        sumDistances += n.Depth;            
                        queue.Enqueue(n);   
                    }               
                }
            }
            return sumDistances;
        }       

        public void Play(byte value)            
        {
            //merge root node with other neighbours nodes, if color is matching
            Root.Value = value;
            List<Node> neighboursToRemove = Root.Neighbours.Where(x => x.Value == value).ToList();
            List<Node> neighboursToAdd = neighboursToRemove.SelectMany(x => x.Neighbours).Except((new Node[] { Root }).Concat(Root.Neighbours)).ToList();

            foreach(Node n in neighboursToRemove)
            {
                foreach(Node m in n.Neighbours)
                {
                    m.Neighbours.Remove(n);
                }
                Nodes.Remove(n);
            }   

            foreach(Node n in neighboursToAdd)
            {
                Root.Neighbours.Add(n);         
                n.Neighbours.Add(Root); 
            }           

            //re-synchronize node index
            for(int i = 0 ; i < Nodes.Count ; i++)
            {
                Nodes[i].Index = i;
            }           
            Solution.Add(value);
        }

        public bool IsSolved()
        {           
            //return Nodes.Count == 1;
            return Root.Neighbours.Count == 0;  
        }           
    }


    class Program
    {       
        public static List<byte> Solve(Board input)
        {
            //A* Pathfinding            
            LinkedList<Board> open = new LinkedList<Board>();       
            input.EstimatedCost = input.GetEstimatedCost();
            open.AddLast(input);            

            while(open.Count > 0)
            {                       
                Board current = open.First.Value;
                open.RemoveFirst();

                if(current.IsSolved())
                {
                    return current.Solution;                
                }
                else
                {
                    //play all neighbours nodes colors
                    foreach(byte i in current.Root.Neighbours.Select(x => x.Value).Distinct())
                    {                       
                        Board newBoard = new Board(current);
                        newBoard.Play(i);           
                        newBoard.EstimatedCost = newBoard.GetEstimatedCost();   

                        //insert board to open list
                        bool inserted = false;
                        for(LinkedListNode<Board> node = open.First ; node != null ; node = node.Next)
                        {                               
                            if(node.Value.EstimatedCost > newBoard.EstimatedCost)
                            {
                                open.AddBefore(node, newBoard);
                                inserted = true;
                                break;
                            }
                        }       
                        if(!inserted)
                            open.AddLast(newBoard);                                                 
                    }   
                }   
            }
            throw new Exception(); //no solution found, impossible
        }   

        public static void Main(string[] args)
        {                   
            using (StreamReader sr = new StreamReader("floodtest"))
            {   
                while(!sr.EndOfStream)
                {                               
                    List<Board> boards = new List<Board>();
                    while(!sr.EndOfStream && boards.Count < 100)
                    {
                        Board board = new Board(sr);                        
                        sr.ReadLine(); //skip empty line
                        boards.Add(board);
                    }                                           
                    List<byte>[] solutions = new List<byte>[boards.Count];                                          
                    Parallel.For(0, boards.Count, i => 
                    {                               
                        solutions[i] = Solve(boards[i]); 
                    });                                         
                    foreach(List<byte> solution in solutions)
                    {
                        Console.WriteLine(string.Join(string.Empty, solution));                                             
                    }       
                }               
            }
        }
    }
}

Mais detalhes sobre como funciona:

Ele usa o algoritmo A * Pathfinding .

O difícil é encontrar um bom heuristic. Se heuristicsubestimar o custo, ele funcionará quase como o algoritmo Dijkstra e, devido à complexidade de uma placa 19x19 com 6 cores, será executada para sempre. Se superestimar o custo, convergirá rapidamente para uma solução, mas não dará bons resultados (algo como 26 movimentos foram 19 possíveis). Encontrar o perfeito heuristicque fornece a quantidade exata exata de etapas para alcançar a solução seria o melhor (seria rápido e forneceria a melhor solução possível). É (a menos que eu esteja errado) impossível. Na verdade, é necessário resolver primeiro o quadro, enquanto é isso que você está realmente tentando fazer (problema de galinha e ovo)

Eu tentei muitas coisas, aqui está o que funcionou para o heuristic:

  • Eu construo um gráfico do quadro atual para avaliar. Cada nodeum representa um conjunto de quadrados contíguos da mesma cor. Usando isso graph, posso calcular facilmente a distância mínima exata do centro para qualquer outro nó. Por exemplo, a distância do centro para o canto superior esquerdo seria 10, porque pelo menos 10 cores as separam.
  • Para calcular heuristic: jogo o tabuleiro atual até o fim. Para cada etapa, escolho a cor que minimizará a soma das distâncias da raiz até todos os outros nós.
  • O número de movimentos necessários para atingir esse fim é o heuristic.

  • Estimated cost(usado por A *) = moves so far+heuristic

Não é perfeito, pois superestima um pouco o custo (portanto, é encontrada uma solução não ideal). De qualquer forma, é rápido calcular e fornecer bons resultados.

Consegui obter uma grande melhoria de velocidade usando o gráfico para executar todas as operações. No começo, eu tinha uma 2-dimensionmatriz. Eu a inundo e recalculo o gráfico quando necessário (por exemplo: para calcular a heurística). Agora tudo é feito usando o gráfico, calculado apenas no início. Para simular etapas, o preenchimento de inundação não é mais necessário; em vez disso, mesclo nós. Isso é muito mais rápido.


2
Por favor, não use code blockspara enfatizar o texto. Temos itálico e negrito para isso.
Fund Monica's Lawsuit

10

Python - 10.800.000 etapas

Como uma solução de referência de último local, considere esta sequência:

print "123456" * 18

Andar de bicicleta por todos os ntempos das cores significa que todos os npassos quadrados terão a mesma cor que o quadrado central. Cada quadrado fica a no máximo 18 passos do centro, então 18 ciclos garantirão que todos os quadrados sejam da mesma cor. Provavelmente, terminará em menos que isso, mas o programa não precisa parar assim que todos os quadrados tiverem a mesma cor; é apenas muito mais benéfico fazer isso. Esse procedimento constante é de 108 etapas por caso de teste, para um total de 10.800.000.


Método de força bruta, sério ...? Joe, pensei que você tivesse um pouco mais de experiência para conhecer melhor, companheiro?
precisa

2
Não é uma entrada séria. Observe que eu o coloquei especificamente como uma solução para agir como um catch-all em último lugar . Qualquer entrada séria teria uma pontuação muito menor que esta.
Joe Z.

Não deveria haver espaços? Como em 1 2 3 4 5 6 ...vez de sua solução atual que dá 123456....
usar o seguinte comando

1
Seria o algoritmo ideal para o código de golfe (em algum outro idioma, embora "print" seja muitos caracteres).
Cruncher

1
Também não acho que o pior caso de 18 etapas seja possível . Mas é claro que nós sabemos que não há nenhum caso pior do que isso, então isso definitivamente funciona :)
Cruncher

8

Java - 2.480.714 etapas

Cometi um pequeno erro antes (coloquei uma frase crucial antes de um loop em vez de no loop:

import java.io.*;

public class HerjanPaintAI {

    BufferedReader r;
    String[] map = new String[19];
    char[][] colors = new char[19][19];
    boolean[][] reached = new boolean[19][19], checked = new boolean[19][19];
    int[] colorCounter = new int[6];
    String answer = "";
    int mapCount = 0, moveCount = 0;

    public HerjanPaintAI(){
        nextMap();

        while(true){

            int bestMove = nextRound();
            answer += bestMove;
            char t = Character.forDigit(bestMove, 10);
            for(int x = 0; x < 19; x++){
                for(int y = 0; y < 19; y++){
                    if(reached[x][y]){
                        colors[x][y] = t;
                    }else if(checked[x][y]){
                        if(colors[x][y] == t){
                            reached[x][y] = true;
                        }
                    }
                }
            }

            boolean gameOver = true;
            for(int x = 0; x < 19; x++){
                for(int y = 0; y < 19; y++){
                    if(!reached[x][y]){
                        gameOver = false;
                        break;
                    }
                }
            }

            for(int x = 0; x < 19; x++){
                for(int y = 0; y < 19; y++){
                    checked[x][y] = false;
                }
            }
            for(int i = 0; i < 6; i++)
                colorCounter[i] = 0;

            if(gameOver)
                nextMap();
        }
    }

    int nextRound(){
        for(int x = 0; x < 19; x++){
            for(int y = 0; y < 19; y++){
                if(reached[x][y]){//check what numbers are next to the reached numbers...
                    check(x, y);
                }
            }
        }

        int[] totalColorCount = new int[6];
        for(int x = 0; x < 19; x++){
            for(int y = 0; y < 19; y++){
                totalColorCount[Character.getNumericValue(colors[x][y])-1]++;
            }
        }

        for(int i = 0; i < 6; i++){
            if(totalColorCount[i] != 0 && totalColorCount[i] == colorCounter[i]){//all of this color can be reached
                return i+1;
            }
        }

        int index = -1, number = 0;
        for(int i = 0; i < 6; i++){
            if(colorCounter[i] > number){
                index = i;
                number = colorCounter[i];
            }
        }

        return index+1;
    }

    void check(int x, int y){
        if(x<18)
            handle(x+1, y, x, y);
        if(x>0)
            handle(x-1, y, x, y);
        if(y<18)
            handle(x, y+1, x, y);
        if(y>0)
            handle(x, y-1, x, y);
    }

    void handle(int x2, int y2, int x, int y){
        if(!reached[x2][y2] && !checked[x2][y2]){
            checked[x2][y2] = true;
            if(colors[x2][y2] == colors[x][y]){
                reached[x2][y2] = true;
                check(x2, y2);
            }else{
                colorCounter[Character.getNumericValue(colors[x2][y2])-1]++;
                checkAround(x2, y2);
            }
        }
    }

    void checkAround(int x2, int y2){
        if(x2<18)
            handleAround(x2+1, y2, x2, y2);
        if(x2>0)
            handleAround(x2-1, y2, x2, y2);
        if(y2<18)
            handleAround(x2, y2+1, x2, y2);
        if(y2>0)
            handleAround(x2, y2-1, x2, y2);
    }

    void handleAround(int x2, int y2, int x, int y){
        if(!reached[x2][y2] && !checked[x2][y2]){
            if(colors[x2][y2] == colors[x][y]){
                checked[x2][y2] = true;
                colorCounter[Character.getNumericValue(colors[x2][y2])-1]++;
                checkAround(x2, y2);
            }
        }
    }

    void nextMap(){
        moveCount += answer.length();
        System.out.println(answer);
        answer = "";

        for(int x = 0; x < 19; x++){
            for(int y = 0; y < 19; y++){
                reached[x][y] = false;
            }
        }

        reached[9][9] = true;

        try {
            if(r == null)
                r = new BufferedReader(new FileReader("floodtest"));

            for(int i = 0; i < 19; i++){
                map[i] = r.readLine();
            }
            r.readLine();//empty line

            if(map[0] == null){
                System.out.println("Maps solved: " + mapCount + " Steps: " + moveCount);
                r.close();
                System.exit(0);
            }
        } catch (Exception e) {e.printStackTrace();}

        mapCount++;

        for(int x = 0; x < 19; x++){
            colors[x] = map[x].toCharArray();
        }
    }

    public static void main(String[] a){
        new HerjanPaintAI();
    }
}

Quanto tempo isso levou para ser executado?
alexander-Brett

@ ali0sha Meu pc não tomou nem a metade de um minuto
Herjan

Bem, porcaria. Mina foi executado por meia hora ...
alexander-Brett

A propósito, o golfe não é obrigatório.
Joe Z.

1
@ m.buettner Falando no diabo, alguém fez amarrar esta solução (e tinha um código menor), três horas depois que você disse isso.
Joe Z.

5

C - 2.075.452

Sei que estou extremamente atrasado para a festa, mas vi esse desafio e queria experimentar.

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

uint64_t rand_state;

uint64_t rand_u64(void) {
    return (rand_state = rand_state * 6364136223846793005ULL + 1442695040888963407ULL);
}

uint64_t better_rand_u64(void) {
    uint64_t r = rand_u64();
    r ^= ((r >> 32) >> (r >> 60));
    return r + 1442695040888963407ULL;
}

uint32_t rand_u32(void) {return rand_u64() >> 32;}

float normal(float mu, float sigma) {
    uint64_t t = 0;
    for (int i = 0; i < 6; i++) {
        uint64_t r = rand_u64();
        uint32_t a = r;
        uint32_t b = r >> 32;
        t += a;
        t += b;
    }
    return ((float)t / (float)UINT32_MAX - 6) * sigma + mu;
}

typedef struct {
    uint8_t x;
    uint8_t y;
} Position;

#define ncolors 6
#define len 19
#define cells (len * len)
#define max_steps (len * (ncolors - 1))
#define center_x 9
#define center_y 9
#define center ((Position){center_x, center_y})

uint64_t zobrist_table[len][len];

void init_zobrist() {
    for (int y = 0; y < len; y++) {
        for (int x = 0; x < len; x++) {
            zobrist_table[y][x] = better_rand_u64();
        }
    }
}

typedef struct {
    uint64_t hash;
    uint8_t grid[len][len];
    bool interior[len][len];
    int boundary_size;
    Position boundary[cells];
} Grid;


void transition(Grid* grid, uint8_t color, int* surrounding_counts) {
    int i = 0;
    while (i < grid->boundary_size) {
        Position p = grid->boundary[i];
        uint8_t x = p.x;
        uint8_t y = p.y;
        bool still_boundary = false;
        for (int dx = -1; dx <= 1; dx++) {
            for (int dy = -1; dy <= 1; dy++) {
                if (!(dx == 0 || dy == 0)) {
                    continue;
                }
                int8_t x1 = x + dx;
                if (!(0 <= x1 && x1 < len)) {
                    continue;
                }
                int8_t y1 = y + dy;
                if (!(0 <= y1 && y1 < len)) {
                    continue;
                }
                if (grid->interior[y1][x1]) {
                    continue;
                }
                uint8_t color1 = grid->grid[y1][x1];
                if (color1 == color) {
                    grid->boundary[grid->boundary_size++] = (Position){x1, y1};
                    grid->interior[y1][x1] = true;
                    grid->hash ^= zobrist_table[y1][x1];
                } else {
                    surrounding_counts[color1]++;
                    still_boundary = true;
                }
            }
        }
        if (still_boundary) {
            i += 1;
        } else {
            grid->boundary[i] = grid->boundary[--grid->boundary_size]; 
        }
    }
}

void reset_grid(Grid* grid, int* surrounding_counts) {
    grid->hash = 0;
    memset(surrounding_counts, 0, ncolors * sizeof(int)); 
    memset(&grid->interior, 0, sizeof(grid->interior));
    grid->interior[center_y][center_x] = true;
    grid->boundary_size = 0;
    grid->boundary[grid->boundary_size++] = center; 
    transition(grid, grid->grid[center_y][center_x], surrounding_counts);
}

bool load_grid(FILE* fp, Grid* grid) {
    memset(grid, 0, sizeof(*grid));
    char buf[19 + 2];
    size_t row = 0;
    while ((fgets(buf, sizeof(buf), fp)) && row < 19) {
        if (strlen(buf) != 20) {
            break;
        }
        for (int i = 0; i < 19; i++) {
            if (!('1' <= buf[i] && buf[i] <= '6')) {
                return false;
            }
            grid->grid[row][i] = buf[i] - '1';
        }
        row++;
    }
    return row == 19;
}

typedef struct Node Node;

struct Node {
    uint64_t hash;
    float visit_counts[ncolors];
    float mean_cost[ncolors];
    float sse[ncolors];
};

#define iters 15000
#define pool_size 32768
#define pool_nodes (pool_size + 100)
#define pool_mask (pool_size - 1)

Node pool[pool_nodes];

void init_node(Node* node, uint64_t hash, int* surrounding_counts) {
    node->hash = hash;
    for (int i = 0; i < ncolors; i++) {
        if (surrounding_counts[i]) {
            node->visit_counts[i] = 1;
            node->mean_cost[i] = 20;
            node->sse[i] = 400;
        }
    }
}

Node* lookup_node(uint64_t hash) {
    size_t index = hash & pool_mask;
    for (int i = index;; i++) {
        uint64_t h = pool[i].hash;
        if (h == hash || !h) {
            return pool + i;
        }
    }
}

int rollout(Grid* grid, int* surrounding_counts, char* solution) {
    for (int i = 0;; i++) {
        int nonzero = 0;
        uint8_t colors[6];
        for (int i = 0; i < ncolors; i++) {
            if (surrounding_counts[i]) {
                colors[nonzero++] = i;
            }
        }
        if (!nonzero) {
            return i;
        }
        uint8_t color = colors[rand_u32() % nonzero]; 
        *(solution++) = color;
        assert(grid->boundary_size);
        memset(surrounding_counts, 0, 6 * sizeof(int));
        transition(grid, color, surrounding_counts);
    }
}

int simulate(Node* node, Grid* grid, int depth, char* solution) {
    float best_cost = INFINITY;
    uint8_t best_color = 255;
    for (int color = 0; color < ncolors; color++) {
        float n = node->visit_counts[color];
        if (node->visit_counts[color] == 0) {
            continue;
        }
        float sigma = sqrt(node->sse[color] / (n * n));
        float cost = node->mean_cost[color];
        cost = normal(cost, sigma);
        if (cost < best_cost) {
            best_color = color;
            best_cost = cost;
        }
    }
    if (best_color == 255) {
        return 0;
    }
    *solution++ = best_color;
    int score;
    int surrounding_counts[ncolors] = {0};
    transition(grid, best_color, surrounding_counts);
    Node* child = lookup_node(grid->hash);
    if (!child->hash) {
        init_node(child, grid->hash, surrounding_counts);
        score = rollout(grid, surrounding_counts, solution);
    } else {
        score = simulate(child, grid, depth + 1, solution);
    }
    score++;
    float n1 = ++node->visit_counts[best_color];
    float u0 = node->mean_cost[best_color];
    float u1 = node->mean_cost[best_color] = u0 + (score - u0) / n1;
    node->sse[best_color] += (score - u0) * (score - u1);
    return score;
}

int main(void) {
    FILE* fp;
    if (!(fp = fopen("floodtest", "r"))) {
        return 1;
    }
    Grid grid;
    init_zobrist();
    while (load_grid(fp, &grid)) {

        memset(pool, 0, sizeof(pool));
        int surrounding_counts[ncolors] = {0};

        reset_grid(&grid, surrounding_counts);
        Node root = {0};

        init_node(&root, grid.hash, surrounding_counts);

        char solution[max_steps] = {0};
        char best_solution[max_steps] = {0};

        int min_score = INT_MAX;

        uint64_t prev_hash = 0;
        uint64_t hash = 0;
        int same_count = 0;

        for (int iter = 0; iter < iters; iter++) {
            reset_grid(&grid, surrounding_counts);
            int score = simulate(&root, &grid, 1, solution);
            if (score < min_score) {
                min_score = score;
                memcpy(best_solution, solution, score);
            }
            hash = 0;
            for (int i = 0; i < score; i++) {
                hash ^= zobrist_table[i%len][(int)solution[i]];
            }
            if (hash == prev_hash) {
                same_count++;
                if (same_count >= 10) {
                    break;
                }
            } else {
                same_count = 0;
                prev_hash = hash;
            }
        }
        int i;
        for (i = 0; i < min_score; i++) {
            best_solution[i] += '1';
        }
        best_solution[i++] = '\n';
        best_solution[i++] = '\0';
        printf(best_solution);
        fflush(stdout);
    }
    return 0;
}

O algoritmo é baseado na Pesquisa em árvore Monte-Carlo com amostragem Thompson e em uma tabela de transposição para reduzir o espaço de pesquisa. Demora cerca de 12 horas na minha máquina. Se você deseja verificar os resultados, pode encontrá-los em https://dropfile.to/pvjYDMV .


O usuário smack42 sugere mudar hash ^= zobrist_table[i][(int)solution[i]];para hash ^= zobrist_table[i%len][(int)solution[i]];para corrigir a falha do programa.
Stephen

@ Stephanie Não vejo como uma pontuação pode ser maior que a média. Você tem uma entrada que faça essa falha? Você tem um link para sua conversa com smak42? Mesmo que não possa travar, suponho que não há mal algum em estar do lado seguro com códigos que não são críticos para o desempenho.
user1502040

Não, desculpe, estava nas edições sugeridas. Aqui está a revisão: codegolf.stackexchange.com/review/suggested-edits/42008
Stephen

+1 por me bater nisso. Mas cuidado, pode haver algumas melhorias que virão;)
tigrou

4

Java - 2.434.108 2.588.847 etapas

Atualmente vencendo (~ 46K à frente de Herjan) em 26/4

Welp, o MrBackend não apenas me derrotou, como também encontrei um bug que produzia uma pontuação enganosamente boa. Está consertado agora (também estava no verificador! Ack), mas infelizmente não tenho tempo no momento para tentar recuperar a coroa. Tentará mais tarde.

Isso se baseia na minha outra solução, mas, em vez de pintar com a cor mais comum nas bordas de preenchimento, pinta com a cor que resultará na exposição de bordas com muitos quadrados adjacentes da mesma cor. Chame de LookAheadPainter. Vou jogar depois, se necessário.

import java.io.*;
import java.util.*;

public class LookAheadPainter {

    static final boolean PRINT_FULL_OUTPUT = true;

    public static void main(String[] a) throws IOException {

        int totalSteps = 0, numSolved = 0;

        char[] board = new char[361];
        Scanner s = new Scanner(new File("floodtest"));
        long startTime = System.nanoTime();

        caseloop: while (s.hasNextLine()) {
            for (int l = 0; l < 19; l++) {
                String line = s.nextLine();
                if (line.isEmpty())
                    continue caseloop;
                System.arraycopy(line.toCharArray(), 0, board, l * 19, 19);
            }

            List<Character> colorsUsed = new ArrayList<>();

            for (;;) {

                FillResult fill = new FillResult(board, board[180], (char) 48, null);

                if (fill.nodesFilled.size() == 361)
                    break;

                int[] branchSizes = new int[7];

                for (int i = 1; i < 7; i++) {
                    List<Integer> seeds = new ArrayList<>();
                    for (Integer seed : fill.edges)
                        if (board[seed] == i + 48)
                            seeds.add(seed);

                    branchSizes[i] = new FillResult(fill.filledBoard, (char) (i + 48), (char) 48, seeds).nodesFilled.size();
                }

                int maxSize = 0;
                char bestColor = 0;

                for (int i = 1; i < 7; i++)
                    if (branchSizes[i] > maxSize) {
                        maxSize = branchSizes[i];
                        bestColor = (char) (i + 48);
                    }

                for (int i : fill.nodesFilled)
                    board[i] = bestColor;

                colorsUsed.add(bestColor);
                totalSteps++;
            }
            numSolved++;

            if (PRINT_FULL_OUTPUT) {
                if (numSolved % 1000 == 0)
                    System.out.println("Solved: " + numSolved); // So you know it's working
                String out = "";
                for (Character c : colorsUsed)
                    out += c;
                System.out.println(out);
            }

        }
        s.close();
        System.out.println("\nTotal steps to solve all cases: " + totalSteps);
        System.out.printf("\nSolved %d test cases in %.2f seconds", numSolved, (System.nanoTime() - startTime) / 1000000000.);
    }

    private static class FillResult {

        Set<Integer> nodesFilled, edges;
        char[] filledBoard;

        FillResult(char[] board, char target, char replacement, List<Integer> seeds) {
            Stack<Integer> nodes = new Stack<>();
            nodesFilled = new HashSet<>();
            edges = new HashSet<>();

            if (seeds == null)
                nodes.push(180);
            else
                for (int i : seeds)
                    nodes.push(i);

            filledBoard = new char[361];
            System.arraycopy(board, 0, filledBoard, 0, 361);

            while (!nodes.empty()) {
                int n = nodes.pop();
                if (n < 0 || n > 360)
                    continue;
                if (filledBoard[n] == target) {
                    filledBoard[n] = replacement;
                    nodesFilled.add(n);
                    if (n % 19 > 0)
                        nodes.push(n - 1);
                    if (n % 19 < 18)
                        nodes.push(n + 1);
                    if (n / 19 > 0)
                        nodes.push(n - 19);
                    if (n / 19 < 18)
                        nodes.push(n + 19);
                } else
                    edges.add(n);
            }
        }
    }
}

EDIT: escrevi um verificador, sinta-se à vontade para usá-lo, ele espera um arquivo steps.txt contendo as etapas que o seu programa produz, bem como o arquivo floodtest: Edit-Edit: (Veja OP)

Se alguém encontrar um problema, informe-o!


Nice, Pizza! E esse verificador é realmente inteligente! O OP deveria ter criado algo como isto / um programa de controle (que teria resolvido muitos problemas).
precisa saber é o seguinte

3

C - 2.480.714 etapas

Ainda não é o ideal, mas agora é mais rápido e tem melhor pontuação.

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

char map[19][19], reach[19][19];
int reachsum[6], totalsum[6];

bool loadmap(FILE *fp)
{
    char buf[19 + 2];
    size_t row = 0;

    while (fgets(buf, sizeof buf, fp) && row < 19) {
        if (strlen(buf) != 20)
            break;
        memcpy(map[row++], buf, 19);
    }
    return row == 19;
}

void calcreach(bool first, size_t row, size_t col);
void check(char c, bool first, size_t row, size_t col)
{
    if (map[row][col] == c)
        calcreach(first, row, col);
    else if (first)
        calcreach(false, row, col);
}

void calcreach(bool first, size_t row, size_t col)
{
    char c = map[row][col];

    reach[row][col] = c;
    reachsum[c - '1']++;
    if (row < 18 && !reach[row + 1][col])
        check(c, first, row + 1, col);
    if (col < 18 && !reach[row][col + 1])
        check(c, first, row, col + 1);
    if (row > 0 && !reach[row - 1][col])
        check(c, first, row - 1, col);
    if (col > 0 && !reach[row][col - 1])
        check(c, first, row, col - 1);
}

void calctotal()
{
    size_t row, col;

    for (row = 0; row < 19; row++)
        for (col = 0; col < 19; col++)
            totalsum[map[row][col] - '1']++;
}

void apply(char c)
{
    char d = map[9][9];
    size_t row, col;

    for (row = 0; row < 19; row++)
        for (col = 0; col < 19; col++)
            if (reach[row][col] == d)
                map[row][col] = c;
}

int main()
{
    char c, best;
    size_t steps = 0;
    FILE *fp;

    if (!(fp = fopen("floodtest", "r")))
        return 1;

    while (loadmap(fp)) {
        do {
            memset(reach, 0, sizeof reach);
            memset(reachsum, 0, sizeof reachsum);
            calcreach(true, 9, 9);
            if (reachsum[map[9][9] - '1'] == 361)
                break;

            memset(totalsum, 0, sizeof totalsum);
            calctotal();

            reachsum[map[9][9] - '1'] = 0;
            for (best = 0, c = 0; c < 6; c++) {
                if (!reachsum[c])
                    continue;
                if (reachsum[c] == totalsum[c]) {
                    best = c;
                    break;
                } else if (reachsum[c] > reachsum[best]) {
                    best = c;
                }
            }

            apply(best + '1');
        } while (++steps);
    }

    fclose(fp);

    printf("steps: %zu\n", steps);
    return 0;
}

Muito bem, Willem, obrigado por me mencionar na sua descrição. Estou honrado por sua graça.
Herjan

Não tem problema, querida Herjan
SteelTermite

A propósito, a sua afirmação "ela pontua marginalmente melhor que a de Herjan" já está desatualizada, eu apenas apliquei a melhoria de que falei (no correio);) Boa sorte me vencendo agora!
Herjan

1
515 passos à frente de você, já ouviu falar de adicionar / remover um '=', em uma comparação, heheh
Herjan

De fato, Herjan. Atualizarei minha inscrição de acordo com sua sugestão.
precisa saber é o seguinte

3

Java - 2.245.529 2.201.995 etapas

Pesquisa em árvore paralela e em cache na profundidade 5, minimizando o número de "ilhas". Como a melhoria da profundidade 4 para a profundidade 5 foi muito pequena, acho que não há muito sentido em melhorar muito mais. Mas, se precisar de melhorias, meu instinto diz trabalhar com o cálculo do número de ilhas como uma diferença entre dois estados, em vez de recalcular tudo.

Atualmente, gera para stdout, até eu conhecer o formato de entrada do verificador.

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class FloodPaint {

    private static final ForkJoinPool FORK_JOIN_POOL = new ForkJoinPool();

    public static void main(String[] arg) throws IOException, InterruptedException, ExecutionException {
        try (BufferedReader reader = new BufferedReader(new FileReader("floodtest"))) {
            int sum = 0;
            State initState = readNextInitState(reader);
            while (initState != null) {
                List<Integer> solution = generateSolution(initState);
                System.out.println(solution);
                sum += solution.size();
                initState = readNextInitState(reader);
            }
            System.out.println(sum);
        }
    }

    private static State readNextInitState(BufferedReader reader) throws IOException {
        int[] initGrid = new int[State.DIM * State.DIM];
        String line = reader.readLine();
        while ((line != null) && line.isEmpty()) {
            line = reader.readLine();
        }
        if (line == null) {
            return null;
        }
        for (int rowNo = 0; rowNo < State.DIM; ++rowNo) {
            for (int colNo = 0; colNo < State.DIM; ++colNo) {
                initGrid[(State.DIM * rowNo) + colNo] = line.charAt(colNo) - '0';
            }
            line = reader.readLine();
        }
        return new State(initGrid);
    }

    private static List<Integer> generateSolution(State initState) throws InterruptedException, ExecutionException {
        List<Integer> solution = new LinkedList<>();
        StateFactory stateFactory = new StateFactory();
        State state = initState;
        while (!state.isSolved()) {
            int num = findGoodNum(state, stateFactory);
            solution.add(num);
            state = state.getNextState(num, stateFactory);
        }
        return solution;
    }

    private static int findGoodNum(State state, StateFactory stateFactory) throws InterruptedException, ExecutionException {
        SolverTask task = new SolverTask(state, stateFactory);
        FORK_JOIN_POOL.invoke(task);
        return task.get();
    }

}

class SolverTask extends RecursiveTask<Integer> {

    private static final int DEPTH = 5;

    private final State state;
    private final StateFactory stateFactory;

    SolverTask(State state, StateFactory stateFactory) {
        this.state = state;
        this.stateFactory = stateFactory;
    }

    @Override
    protected Integer compute() {
        try {
            Map<Integer,AnalyzerTask> tasks = new HashMap<>();
            for (int num = 1; num <= 6; ++num) {
                if (num != state.getCenterNum()) {
                    State nextState = state.getNextState(num, stateFactory);
                    AnalyzerTask task = new AnalyzerTask(nextState, DEPTH - 1, stateFactory);
                    tasks.put(num, task);
                }
            }
            invokeAll(tasks.values());
            int bestValue = Integer.MAX_VALUE;
            int bestNum = -1;
            for (Map.Entry<Integer,AnalyzerTask> taskEntry : tasks.entrySet()) {
                int value = taskEntry.getValue().get();
                if (value < bestValue) {
                    bestValue = value;
                    bestNum = taskEntry.getKey();
                }
            }
            return bestNum;
        } catch (InterruptedException | ExecutionException ex) {
            throw new RuntimeException(ex);
        }
    }

}

class AnalyzerTask extends RecursiveTask<Integer> {

    private static final int DEPTH_THRESHOLD = 3;

    private final State state;
    private final int depth;
    private final StateFactory stateFactory;

    AnalyzerTask(State state, int depth, StateFactory stateFactory) {
        this.state = state;
        this.depth = depth;
        this.stateFactory = stateFactory;
    }

    @Override
    protected Integer compute() {
        return (depth < DEPTH_THRESHOLD) ? analyze() : split();
    }

    private int analyze() {
        return analyze(state, depth);
    }

    private int analyze(State state, int depth) {
        if (state.isSolved()) {
            return -depth;
        }
        if (depth == 0) {
            return state.getNumIslands();
        }
        int bestValue = Integer.MAX_VALUE;
        for (int num = 1; num <= 6; ++num) {
            if (num != state.getCenterNum()) {
                State nextState = state.getNextState(num, stateFactory);
                int nextValue = analyze(nextState, depth - 1);
                bestValue = Math.min(bestValue, nextValue);
            }
        }
        return bestValue;
    }

    private int split() {
        try {
            if (state.isSolved()) {
                return -depth;
            }
            Collection<AnalyzerTask> tasks = new ArrayList<>(5);
            for (int num = 1; num <= 6; ++num) {
                State nextState = state.getNextState(num, stateFactory);
                AnalyzerTask task = new AnalyzerTask(nextState, depth - 1, stateFactory);
                tasks.add(task);
            }
            invokeAll(tasks);
            int bestValue = Integer.MAX_VALUE;
            for (AnalyzerTask task : tasks) {
                int nextValue = task.get();
                bestValue = Math.min(bestValue, nextValue);
            }
            return bestValue;
        } catch (InterruptedException | ExecutionException ex) {
            throw new RuntimeException(ex);
        }
    }

}

class StateFactory {

    private static final int INIT_CAPACITY = 40000;
    private static final float LOAD_FACTOR = 0.9f;

    private final ReadWriteLock cacheLock = new ReentrantReadWriteLock();
    private final Map<List<Integer>,State> cache = new HashMap<>(INIT_CAPACITY, LOAD_FACTOR);

    State get(int[] grid) {
        List<Integer> stateKey = new IntList(grid);
        State state;
        cacheLock.readLock().lock();
        try {
            state = cache.get(stateKey);
        } finally {
            cacheLock.readLock().unlock();
        }
        if (state == null) {
            cacheLock.writeLock().lock();
            try {
                state = cache.get(stateKey);
                if (state == null) {
                    state = new State(grid);
                    cache.put(stateKey, state);
                }
            } finally {
                cacheLock.writeLock().unlock();
            }
        }
        return state;
    }

}

class State {

    static final int DIM = 19;
    private static final int CENTER_INDEX = ((DIM * DIM) - 1) / 2;

    private final int[] grid;
    private int numIslands;

    State(int[] grid) {
        this.grid = grid;
        numIslands = calcNumIslands(grid);
    }

    private static int calcNumIslands(int[] grid) {
        int numIslands = 0;
        BitSet uncounted = new BitSet(DIM * DIM);
        uncounted.set(0, DIM * DIM);
        int index = -1;
        while (!uncounted.isEmpty()) {
            index = uncounted.nextSetBit(index + 1);
            BitSet island = new BitSet(DIM * DIM);
            generateIsland(grid, index, grid[index], island);
            ++numIslands;
            uncounted.andNot(island);
        }
        return numIslands;
    }

    private static void generateIsland(int[] grid, int index, int num, BitSet island) {
        if ((grid[index] == num) && !island.get(index)) {
            island.set(index);
            if ((index % DIM) > 0) {
                generateIsland(grid, index - 1, num, island);
            }
            if ((index % DIM) < (DIM - 1)) {
                generateIsland(grid, index + 1, num, island);
            }
            if ((index / DIM) > 0) {
                generateIsland(grid, index - DIM, num, island);
            }
            if ((index / DIM) < (DIM - 1)) {
                generateIsland(grid, index + DIM, num, island);
            }
        }
    }

    int getCenterNum() {
        return grid[CENTER_INDEX];
    }

    boolean isSolved() {
        return numIslands == 1;
    }

    int getNumIslands() {
        return numIslands;
    }

    State getNextState(int num, StateFactory stateFactory) {
        int[] nextGrid = grid.clone();
        if (num != getCenterNum()) {
            flood(nextGrid, CENTER_INDEX, getCenterNum(), num);
        }
        State nextState = stateFactory.get(nextGrid);
        return nextState;
    }

    private static void flood(int[] grid, int index, int fromNum, int toNum) {
        if (grid[index] == fromNum) {
            grid[index] = toNum;
            if ((index % 19) > 0) {
                flood(grid, index - 1, fromNum, toNum);
            }
            if ((index % 19) < (DIM - 1)) {
                flood(grid, index + 1, fromNum, toNum);
            }
            if ((index / 19) > 0) {
                flood(grid, index - DIM, fromNum, toNum);
            }
            if ((index / 19) < (DIM - 1)) {
                flood(grid, index + DIM, fromNum, toNum);
            }
        }
    }

}

class IntList extends AbstractList<Integer> implements List<Integer> {

    private final int[] arr;
    private int hashCode = -1;

    IntList(int[] arr) {
        this.arr = arr;
    }

    @Override
    public int size() {
        return arr.length;
    }

    @Override
    public Integer get(int index) {
        return arr[index];
    }

    @Override
    public Integer set(int index, Integer value) {
        int oldValue = arr[index];
        arr[index] = value;
        return oldValue;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof IntList) {
            IntList arg = (IntList) obj;
            return Arrays.equals(arr, arg.arr);
        }
        return super.equals(obj);
    }

    @Override
    public int hashCode() {
        if (hashCode == -1) {
            hashCode = 1;
            for (int elem : arr) {
                hashCode = 31 * hashCode + elem;
            }
        }
        return hashCode;
    }

}

Impressionante, você pode escrever as etapas em um arquivo? Para que possamos verificá-lo?
Herjan

@ Herjan, parece que seu código é auto-validador. Veja isSolved ()
BurntPizza

@BurntPizza So? Meu código também é auto-validado, lol ... quero dizer, isso pode ser tão errado quanto meu próprio código.
Herjan

isSolved () não é para validação, é para rescisão. Quanto à gravação - fará na próxima versão.
MrBackend

Eu estaria interessado se uma heurística que fizesse a pesquisa em 5 etapas com profundidade apenas se o número de etapas encontradas para 4 fosse mais do 24que resultaria em um tempo de execução muito mais eficiente.
Joe Z.

2

Minha última entrada: C - 2.384.020 etapas

Desta vez, uma opção de 'verificar todas as possibilidades' ... Essa pontuação é obtida com a profundidade definida em 3. A profundidade 5 deve fornecer ~ 2,1 milhões de passos ... MUITO LENTO. O Depth 20+ fornece a menor quantidade de etapas possível (apenas verifica todas as partidas e as vitórias mais curtas) ... Tem a menor quantidade de etapas, embora eu odeie, já que é apenas um pouquinho melhor, mas o desempenho é uma merda. Eu prefiro minha outra entrada C, que também está neste post.

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

char map[19][19], reach[19][19];
int reachsum[6], totalsum[6], mapCount = 0;
FILE *stepfile;

bool loadmap(FILE *fp)
{
    fprintf(stepfile, "%s", "\n");

    mapCount++;

    char buf[19 + 2];
    size_t row = 0;

    while (fgets(buf, sizeof buf, fp) && row < 19) {
        if (strlen(buf) != 20)
            break;
        memcpy(map[row++], buf, 19);
    }
    return row == 19;
}

void calcreach(bool first, size_t row, size_t col);
void check(char c, bool first, size_t row, size_t col)
{
    if (map[row][col] == c)
        calcreach(first, row, col);
    else if (first)
        calcreach(false, row, col);
}

void calcreach(bool first, size_t row, size_t col)
{
    char c = map[row][col];

    reach[row][col] = c;
    reachsum[c - '1']++;
    if (row < 18 && !reach[row + 1][col])
        check(c, first, row + 1, col);
    if (col < 18 && !reach[row][col + 1])
        check(c, first, row, col + 1);
    if (row > 0 && !reach[row - 1][col])
        check(c, first, row - 1, col);
    if (col > 0 && !reach[row][col - 1])
        check(c, first, row, col - 1);
}

void calctotal()
{
    size_t row, col;

    for (row = 0; row < 19; row++)
        for (col = 0; col < 19; col++)
            totalsum[map[row][col] - '1']++;
}

void apply(char c)
{
    char d = map[9][9];
    size_t row, col;

    for (row = 0; row < 19; row++)
        for (col = 0; col < 19; col++)
            if (reach[row][col] == d)
                map[row][col] = c;
}

int pown(int x, int y){
    int p = 1;
    for(int i = 0; i < y; i++){
        p = p * x;
    }

    return p;
}

int main()
{
    size_t steps = 0;
    FILE *fp;

    if (!(fp = fopen("floodtest", "r")))
        return 1;
    if(!(stepfile = fopen("steps.txt", "w")))
        return 1;

    const int depth = 5;
    char possibilities[pown(6, depth)][depth];
    int t = 0;
    for(int a = 0; a < 6; a++){
        for(int b = 0; b < 6; b++){
            for(int c = 0; c < 6; c++){
                for(int d = 0; d < 6; d++){
                    for(int e = 0; e < 6; e++){
                        possibilities[t][0] = (char)(a + '1');
                        possibilities[t][1] = (char)(b + '1');
                        possibilities[t][2] = (char)(c + '1');
                        possibilities[t][3] = (char)(d + '1');
                        possibilities[t++][4] = (char)(e + '1');
                    }
                }
            }
        }
    }
    poes:
    while (loadmap(fp)) {
        do {
            char map2[19][19];
            memcpy(map2, map, sizeof(map));

            memset(reach, 0, sizeof reach);
            memset(reachsum, 0, sizeof reachsum);
            calcreach(true, 9, 9);

            int best = 0, index = 0, end = depth;
            for(int i = 0; i < pown(6, depth); i++){
                for(int d = 0; d < end; d++){

                    apply(possibilities[i][d]);

                    memset(reach, 0, sizeof reach);
                    memset(reachsum, 0, sizeof reachsum);
                    calcreach(true, 9, 9);

                    if(reachsum[map[9][9] - '1'] == 361 && d < end){
                        end = d+1;
                        index = i;
                        break;
                    }
                }
                if(end == depth && best < reachsum[map[9][9] - '1']){
                    best = reachsum[map[9][9] - '1'];
                    index = i;
                }

                memcpy(map, map2, sizeof(map2));
                memset(reach, 0, sizeof reach);
                memset(reachsum, 0, sizeof reachsum);
                calcreach(true, 9, 9);
            }

            for(int d = 0; d < end; d++){

                apply(possibilities[index][d]);

                memset(reach, 0, sizeof reach);
                memset(reachsum, 0, sizeof reachsum);
                calcreach(true, 9, 9);

                fprintf(stepfile, "%c", possibilities[index][d]);
                steps++;
            }
            if(reachsum[map[9][9] - '1'] == 361)
                goto poes;
        } while (1);
    }

    fclose(fp);
    fclose(stepfile);

    printf("steps: %zu\n", steps);
    return 0;
}

Outra IA aprimorada escrita em C - 2.445.761 etapas

Baseado no SteelTermite:

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

char map[19][19], reach[19][19];
int reachsum[6], totalsum[6], mapCount = 0;
FILE *stepfile;

bool loadmap(FILE *fp)
{
    fprintf(stepfile, "%s", "\n");

    if(mapCount % 1000 == 0)
        printf("mapCount = %d\n", mapCount);

    mapCount++;

    char buf[19 + 2];
    size_t row = 0;

    while (fgets(buf, sizeof buf, fp) && row < 19) {
        if (strlen(buf) != 20)
            break;
        memcpy(map[row++], buf, 19);
    }
    return row == 19;
}

void calcreach(bool first, size_t row, size_t col);
void check(char c, bool first, size_t row, size_t col)
{
    if (map[row][col] == c)
        calcreach(first, row, col);
    else if (first)
        calcreach(false, row, col);
}

void calcreach(bool first, size_t row, size_t col)
{
    char c = map[row][col];

    reach[row][col] = c;
    reachsum[c - '1']++;
    if (row < 18 && !reach[row + 1][col])
        check(c, first, row + 1, col);
    if (col < 18 && !reach[row][col + 1])
        check(c, first, row, col + 1);
    if (row > 0 && !reach[row - 1][col])
        check(c, first, row - 1, col);
    if (col > 0 && !reach[row][col - 1])
        check(c, first, row, col - 1);
}

void calctotal()
{
    size_t row, col;

    for (row = 0; row < 19; row++)
        for (col = 0; col < 19; col++)
            totalsum[map[row][col] - '1']++;
}

void apply(char c)
{
    char d = map[9][9];
    size_t row, col;

    for (row = 0; row < 19; row++)
        for (col = 0; col < 19; col++)
            if (reach[row][col] == d)
                map[row][col] = c;
}

int main()
{
    char c, best, answer;
    size_t steps = 0;
    FILE *fp;

    if (!(fp = fopen("floodtest", "r")))
        return 1;
    if(!(stepfile = fopen("steps.txt", "w")))
            return 1;

    while (loadmap(fp)) {
        do {
            memset(reach, 0, sizeof reach);
            memset(reachsum, 0, sizeof reachsum);
            calcreach(true, 9, 9);
            if (reachsum[map[9][9] - '1'] == 361)
                break;

            memset(totalsum, 0, sizeof totalsum);
            calctotal();

            reachsum[map[9][9] - '1'] = 0;
            for (best = 0, c = 0; c < 6; c++) {
                if (!reachsum[c])
                    continue;
                if (reachsum[c] == totalsum[c]) {
                    best = c;
                    goto outLoop;
                } else if (reachsum[c] > reachsum[best]) {
                    best = c;
                }
            }

            char map2[19][19];
            memcpy(map2, map, sizeof(map));

            int temp = best;
            for(c = 0; c < 6; c++){

                if(c != best){

                    apply(c + '1');

                    memset(reach, 0, sizeof reach);
                    memset(reachsum, 0, sizeof reachsum);
                    calcreach(true, 9, 9);
                    if (reachsum[best] == totalsum[best]) {

                        memcpy(map, map2, sizeof(map2));
                        memset(reach, 0, sizeof reach);
                        memset(reachsum, 0, sizeof reachsum);
                        calcreach(true, 9, 9);

                        if(temp == -1)
                            temp = c;
                        else if(reachsum[c] > reachsum[temp])
                            temp = c;
                    }

                    memcpy(map, map2, sizeof(map2));
                    memset(reach, 0, sizeof reach);
                    memset(reachsum, 0, sizeof reachsum);
                    calcreach(true, 9, 9);
                }
            }

outLoop:    answer = (char)(temp + '1');
            fprintf(stepfile, "%c", answer);
            apply(answer);
        } while (++steps);
    }

    fclose(fp);
    fclose(stepfile);

    printf("steps: %zu\n", steps);
    return 0;
}

... e ~ 200K para vencer o meu;)
MrBackend 28/04

Você deve postar cada entrada como uma resposta individual.
Joe Z.

@JoeZ. Desculpe, mas parecia spam, então decidi montá-los em uma resposta (não importa, pois apenas a melhor (a melhor = a IA com menor quantidade de etapas) conta). Pelo menos é o que eu pensava.
Herjan

1

Java - 2.610.797 4.780.841 etapas

(Fill-Bug corrigido, a pontuação agora é drasticamente pior -_-)

Esta é a submissão do meu algoritmo de referência básico, simplesmente cria um histograma dos quadrados nas bordas da área pintada e pinta com a cor mais comum. Executa os 100k em alguns minutos.

Obviamente não vai ganhar, mas certamente não é o último. Provavelmente farei outra submissão para coisas inteligentes. Sinta-se livre para usar esse algoritmo como ponto de partida.

Remova o comentário das linhas comentadas para a saída completa. O padrão é imprimir o número de etapas executadas.

import java.io.*;
import java.util.*;

public class PainterAI {

    public static void main(String[] args) throws IOException {

        int totalSteps = 0, numSolved = 0;

        char[] board = new char[361];
        Scanner s = new Scanner(new File("floodtest"));
        long startTime = System.nanoTime();
        caseloop: while (s.hasNextLine()) {
            for (int l = 0; l < 19; l++) {
                String line = s.nextLine();
                if (line.isEmpty())
                    continue caseloop;
                System.arraycopy(line.toCharArray(), 0, board, l * 19, 19);
            }

            List<Character> colorsUsed = new ArrayList<>();
            Stack<Integer> nodes = new Stack<>();

            for (;; totalSteps++) {
                char p = board[180];
                int[] occurrences = new int[7];
                nodes.add(180);
                int numToPaint = 0;
                while (!nodes.empty()) {
                    int n = nodes.pop();
                    if (n < 0 || n > 360)
                        continue;
                    if (board[n] == p) {
                        board[n] = 48;
                        numToPaint++;
                        if (n % 19 > 0)
                            nodes.push(n - 1);
                        if(n%19<18)
                            nodes.push(n + 1);
                        if(n/19>0)
                            nodes.push(n - 19);
                        if(n/19<18)
                            nodes.push(n + 19);
                    } else
                        occurrences[board[n] - 48]++;
                }
                if (numToPaint == 361)
                    break;
                char mostFrequent = 0;
                int times = -1;
                for (int i = 1; i < 7; i++)
                    if (occurrences[i] > times) {
                        times = occurrences[i];
                        mostFrequent = (char) (i + 48);
                    }
                for (int i = 0; i < 361; i++)
                    if (board[i] == 48)
                        board[i] = mostFrequent;
                //colorsUsed.add(mostFrequent);
            }
            numSolved++;

            /*String out = "";
            for (Character c : colorsUsed)
                out += c;
            System.out.println(out); //print output*/
        }
        s.close();
        System.out.println("Total steps to solve all cases: " + totalSteps);
        System.out.printf("\nSolved %d test cases in %.2f seconds", numSolved, (System.nanoTime() - startTime) / 1000000000.);
    }
}

Golfe a 860 caracteres (sem incluir as novas linhas de formatação), mas poderia ser mais reduzido se eu quisesse:

import java.io.*;import java.util.*;class P{
public static void main(String[]a)throws Exception{int t=0;char[]b=new char[361];
Scanner s=new Scanner(new File("floodtest"));c:while(s.hasNextLine()){
for(int l=0;l<19;l++){String L=s.nextLine();if(L.isEmpty())continue c;
System.arraycopy(L.toCharArray(),0,b,l*19,19);}List<Character>u=new ArrayList<>();
Stack<Integer>q=new Stack<>();for(int[]o=new int[7];;t++){char p=b[180];q.add(180);
int m=0;while(!q.empty()){int n=q.pop();if(n<0|n>360)continue;if(b[n]==p){b[n]=48;m++;
if(n%19>0)q.add(n-1);if(n%19<18)q.add(n+1);if(n/19>0)q.add(n-19);if(n/19<18)
q.add(n+19);}else o[b[n]-48]++;}if(m==361)break;
char f=0;int h=0;for(int i=1;i<7;i++)if(o[i]>h){h=o[i];f=(char)(i+48);}
for(int i=0;i<361;i++)if(b[i]==48)b[i]=f;u.add(f);}String y="";for(char c:u)y+=c;
System.out.println(y);}s.close();System.out.println("Steps: "+t);}}

A única razão pela qual "certamente não é a última" é porque minha solução de referência está lá para resolver as coisas. Na verdade, é o último lugar de todas as submissões de outras pessoas no momento: P
Joe Z.

@JoeZ. Bem, estava na frente do SteelTermite, mas ele melhorou o dele. Eu quis dizer isso como o "próximo passo lógico da ingênua" abordagem. Eu estaria preocupado se ele estava indo bem; P
BurntPizza

1

Haskell - 2.475.056 etapas

O algoritmo é semelhante ao sugerido por MrBackend nos comentários. A diferença é: sua sugestão encontra o caminho mais barato para o quadrado do custo mais alto, o meu avidamente reduz a excentricidade do gráfico a cada passo.

import Data.Array
import qualified Data.Map as M
import Data.Word
import Data.List
import Data.Maybe
import Data.Function (on)
import Data.Monoid
import Control.Arrow
import Control.Monad (liftM)
import System.IO
import System.Environment
import Control.Parallel.Strategies
import Control.DeepSeq

type Grid v = Array (Word8,Word8) v

main = do
  (ifn:_) <- getArgs
  hr <- openFile ifn ReadMode
  sp <- liftM parseFile $ hGetContents hr
  let (len,sol) = turns (map solve sp `using` parBuffer 3 (evalList rseq))
  putStrLn $ intercalate "\n" $ map (concatMap show) sol
  putStrLn $ "\n\nTotal turns: " ++ (show len)

turns :: [[a]] -> (Integer,[[a]])
turns l = rl' 0 l where
  rl' c [] = (c,[])
  rl' c (k:r) = let
   s = c + genericLength k
   (s',l') = s `seq` rl' s r
   in (s',k:l')

centrepoint :: Grid v -> (Word8,Word8)
centrepoint g = let
  ((x0,y0),(x1,y1)) = bounds g
  med l h = let t = l + h in t `div` 2 + t `mod` 2
  in (med x0 x1, med y0 y1)

neighbours :: Grid v -> (Word8,Word8) -> [(Word8,Word8)]
neighbours g (x,y) = filter
  (inRange $ bounds g)
  [(x,y+1),(x+1,y),(x,y-1),(x-1,y)]

areas :: Eq v => Grid v -> [[(Word8,Word8)]]
areas g = p $ indices g where
  p [] = []
  p (a:r) = f : p (r \\ f) where
    f = s g [a] []
s g [] _ = []
s g (h:o) v = let
  n = filter (((==) `on` (g !)) h) $ neighbours g h
  in h : s g ((n \\ (o ++ v)) ++ o) (h : v)

applyFill :: Eq v => v -> Grid v -> Grid v
applyFill c g = g // (zip fa $ repeat c) where
  fa = s g [centrepoint g] []

solve g = solve' gr' where
  aa = areas g
  cp = centrepoint g
  ca = head $ head $ filter (elem cp) aa
  gr' = M.fromList $ map (
    \r1 -> (head r1, map head $ filter (
      \r2 -> head r1 /= head r2 &&
        (not $ null $ intersect (concatMap (neighbours g) r1) r2)
     ) aa
    )
   ) aa
  solve' gr
    | null $ tail $ M.keys $ gr = []
    | otherwise = best : solve' ngr where
      djk _ [] = []
      djk v ((n,q):o) = (n,q) : djk (q:v) (
        o ++ zip (repeat (n+1))
        ((gr M.! q) \\ (v ++ map snd o))
       )
      dout = djk [] [(0,ca)]
      din = let
        m = maximum $ map fst dout
        s = filter ((== m) . fst) dout
        in djk [] s
      rc = filter (flip elem (gr M.! ca) . snd) din
      frc = let
        m = minimum $ map fst rc
        in map snd $ filter ((==m) . fst) rc
      msq = concat $ filter (flip elem frc . head) aa
      clr = map (length &&& head) $ group $ sort $ map (g !) msq
      best = snd $ maximumBy (compare `on` fst) clr
      ngr = let
        ssm = filter ((== best) . (g !)) $ map snd rc
        sml = (concatMap (gr M.!) ssm)
        ncl = ((gr M.! ca) ++ sml) \\ (ca : ssm)
        brk = M.insert ca ncl $ M.filterWithKey (\k _ ->
          (not . flip elem ssm) k
         ) gr
        in M.map 
          (\l -> nub $ map (\e -> if e `elem` ssm then ca else e) l)
          brk


parseFile :: String -> [Grid Word8]
parseFile f = map mk $ filter (not . null . head) $ groupBy ((==) `on` null) $
  map (map ((read :: String -> Word8) . (:[]))) $ lines f where
    mk :: [[Word8]] -> Grid Word8
    mk m = let
      w = fromIntegral (length $ head m) - 1
      h = fromIntegral (length m) - 1
      in array ((0,0),(w,h)) [ ((x,y),v) |
        (y,l) <- zip [h,h-1..] m,
        (x,v) <- zip [0..] l
       ]

showGrid :: Grid Word8 -> String
showGrid g = intercalate "\n" l where
  l = map sl $ groupBy ((==) `on` snd) $
    sortBy ((flip (compare `on` snd)) <> (compare `on` fst)) $
    indices g
  sl = intercalate " " . map (show . (g !))

testsolve = do
  hr <- openFile "floodtest" ReadMode
  sp <- liftM (head . parseFile) $ hGetContents hr
  let
   sol = solve sp
   a = snd $ mapAccumL (\g s -> let g' = applyFill s g in (g',g')) sp sol
  sequence_ $ map (\g -> putStrLn (showGrid g) >> putStrLn "\n") a

Já terminou a execução?
Joe Z.

Ainda não, ele poderia ter terminado agora se eu o deixasse funcionar durante a noite, mas o ventilador estava barulhento, então eu hibernei o computador. Está funcionando novamente agora, irá verificar novamente quando chegar em casa do trabalho.
Lista Jeremy

Ele travou devido a um estouro de pilha, modificando agora para evitar isso.
Lista Jeremy

1

C # - 2.383.569

É um percurso profundo de possíveis soluções que escolhe aproximadamente o caminho da melhor melhoria (semelhante / mesmo que a entrada C de Herjan), exceto que eu inverti inteligentemente a ordem de geração de soluções candidatas depois de ver a Herjan postar os mesmos números. Demora mais de 12 horas a correr.

class Solver
{
    static void Main()
    {
        int depth = 3;
        string text = File.ReadAllText(@"C:\TEMP\floodtest.txt");
        text = text.Replace("\n\n", ".").Replace("\n", "");
        int count = 0;
        string[] tests = text.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
        for (int i = 0; i < tests.Length; i++)
        {
            Solver s = new Solver(tests[i]);
            string k1 = s.solve(depth);
            count += k1.Length;
            Console.WriteLine(((100 * i) / tests.Length) + " " + i + " " + k1.Length + " " + count + " " + k1);
        }
        Console.WriteLine(count);
    }

    public readonly int MAX_DIM;
    public char[] board;
    public Solver(string prob)
    {
        board = read(prob);
        MAX_DIM = (int)Math.Sqrt(board.Length);
    }

    public string solve(int d)
    {
        var sol = "";
        while (score(eval(copy(board), sol)) != board.Length)
        {
            char[] b = copy(board);
            eval(b, sol);

            var canidates = new List<string>();
            buildCanidates("", canidates, d);
            var best = canidates.Select(c => new {score = score(eval(copy(b), c)), sol = c}).ToList().OrderByDescending(t=>t.score).ThenBy(v => v.sol.Length).First();
            sol = sol + best.sol[0];
        }
        return sol;
    }

    public void buildCanidates(string b, List<string> r, int d)
    {
        if(b.Length>0)
            r.Add(b);
        if (d > 0)
        {
            r.Add(b);
            for (char i = '6'; i >= '1'; i--)
                if(b.Length == 0 || b[b.Length-1] != i)
                    buildCanidates(b + i, r, d - 1);
        }
    }

    public char[] read(string s)
    {
        return s.Where(c => c >= '0' && c <= '9').ToArray();
    }

    public void print(char[] b)
    {
        for (int i = 0; i < MAX_DIM; i++)
        {
            for(int j=0; j<MAX_DIM; j++)
                Console.Write(b[i*MAX_DIM+j]);
            Console.WriteLine();
        }
        Console.WriteLine();
    }

    public char[] copy(char[] b)
    {
        char[] n = new char[b.Length];
        for (int i = 0; i < b.Length; i++)
            n[i] = b[i];
        return n;
    }

    public char[] eval(char[] b, string sol)
    {
        foreach (char c in sol)
            eval(b, c);
        return b;
    }

    public void eval(char[] b, char c)
    {
        foreach (var l in flood(b))
            b[l] = c;
    }

    public int score(char[] b)
    {
        return flood(b).Count;
    }

    public List<int> flood(char[] b)
    {
        int start = (MAX_DIM * (MAX_DIM / 2)) + (MAX_DIM / 2);
        var check = new List<int>(MAX_DIM * MAX_DIM);
        bool[] seen = new bool[b.Length];
        var hits = new List<int>(MAX_DIM*MAX_DIM);

        check.Add(start);
        seen[start]=true;
        char target = b[start];

        int at = 0;
        while (at<check.Count)
        {
            int toCheck = check[at++];
            if (b[toCheck] == target)
            {
                addNeighbors(check, seen, toCheck);
                hits.Add(toCheck);
            }
        }
        return hits;
    }

    public void addNeighbors(List<int> check, bool[] seen, int loc)
    {
        int x = loc / MAX_DIM;
        int y = loc % MAX_DIM;
        addNeighbor(check, seen, x, y - 1);
        addNeighbor(check, seen, x, y + 1);
        addNeighbor(check, seen, x - 1, y);
        addNeighbor(check, seen, x + 1, y);
    }

    public void addNeighbor(List<int> check, bool[] seen, int x, int y)
    {
        if (x >= 0 && x < MAX_DIM && y >= 0 && y < MAX_DIM)
        {
            int l = (x * MAX_DIM) + y;
            if (!seen[l])
            {
                seen[l] = true;
                check.Add(l);
            }
        }
    }
}

1

Java - 2.403.189

BUILD SUCCESSFUL (total time: 220 minutes 15 seconds)

Esta deveria ser minha tentativa de uma força bruta. Mas! Minha primeira implementação da escolha "melhor" de profundidade única rendeu:

2,589,328 - BUILD SUCCESSFUL (total time: 3 minutes 11 seconds)

O código usado para ambos é o mesmo com a força bruta armazenando um "instantâneo" dos outros movimentos possíveis e executando o algoritmo sobre todos eles.


  • Problemas

Se estiver sendo executado com a abordagem "multi", ocorrerão falhas aleatórias. Eu configurei as 100 primeiras entradas do quebra-cabeça em um teste de unidade e posso obter 100% de aprovação, mas não 100% do tempo. Para compensar, apenas acompanhei o número do quebra-cabeça atual no momento da falha e iniciei um novo Thread pegando onde o último parou. Cada thread gravou seus respectivos resultados em um arquivo. O conjunto de arquivos foi condensado em um único arquivo.

  • Aproximação

Noderepresenta um bloco / quadrado do quadro e armazena uma referência a todos os seus vizinhos. Acompanhe três Set<Node>variáveis: Remaining, Painted, Targets. Cada iteração procura Targetsagrupar todos os candidatenós por valor, selecionando target valueo número de nós "afetados". Esses nós afetados tornam-se os destinos para a próxima iteração.

A fonte está espalhada por muitas classes e os snippets não são muito significativos longe do contexto do todo. Minha fonte pode ser navegada via GitHub . Eu também brinquei com uma demonstração do JSFiddle para visualização.

No entanto, meu método de cavalo de batalha de Solver.java:

public void flood() {

 final Data data = new Data();
 consolidateCandidates(data, targets);

 input.add(data.getTarget());

 if(input.size() > SolutionRepository.getInstance().getThreshold()){
  //System.out.println("Exceeded threshold: " + input.toString());
  cancelled = true;
 }
 paintable.addAll(data.targets());
 remaining.removeAll(data.targets());

 if(!data.targets().isEmpty()){
  targets = data.potentialTargets(data.targets(), paintable);

  data.setPaintable(paintable);
  data.setRemaining(remaining);
  data.setInput(input);

  SolutionRepository.getInstance().addSnapshot(data, input);
 }
}

1

C # - 2.196.462 2.155.834

Essa é efetivamente a mesma abordagem de 'procurar o melhor descendente' que meu outro solucionador, mas com algumas otimizações que apenas com paralelismo permitem que ela chegue à profundidade 5 em pouco menos de 10 horas. No decorrer do teste, também encontrei um bug no original, de modo que o algoritmo ocasionalmente pegava rotas ineficientes para o estado final (não era responsável pela profundidade dos estados com pontuação = 64; descoberto enquanto brincava com resultados de profundidade = 7)

A principal diferença entre esse e o solucionador anterior é que ele mantém os estados do jogo de inundação na memória, para que não precise regenerar 6 ^ 5 estados. Com base no uso da CPU durante a execução, tenho certeza de que isso mudou da CPU vinculada para a largura de banda da memória. Muito divertido. Tantos pecados.

Edit: Por razões, o algoritmo de profundidade 5 nem sempre produz o melhor resultado. Para melhorar o desempenho, vamos fazer as profundidades 5 ... e 4 ... e 3 e 2 e 1, e ver qual é o melhor. Raspou outros movimentos de 40k. Como a profundidade 5 é a maior parte do tempo, adicionar 4 a 1 apenas aumenta o tempo de execução de ~ 10 horas a ~ 11 horas. Yay!

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Collections.Generic;

public class Program
{
    static void Main()
    {
        int depth = 5;
        string text = File.ReadAllText(@"C:\TEMP\floodtest.txt");
        text = text.Replace("\n\n", ".").Replace("\n", "");
        int count = 0;
        string[] tests = text.Split(new [] { '.' }, StringSplitOptions.RemoveEmptyEntries);

        Stopwatch start = Stopwatch.StartNew();

        const int parChunk = 16*16;
        for (int i = 0; i < tests.Length; i += parChunk)
        {
            //did not know that parallel select didn't respect order
            string[] sols = tests.Skip(i).Take(parChunk).AsParallel().Select((t, idx) => new { s = new Solver2(t).solve(depth), idx}).ToList().OrderBy(v=>v.idx).Select(v=>v.s).ToArray();
            for (int j = 0; j < sols.Length; j++)
            {
                string k1 = sols[j];
                count += k1.Length;
                int k = i + j;
                int estimate = (int)((count*(long)tests.Length)/(k+1));
                Console.WriteLine(k + "\t" + start.Elapsed.TotalMinutes.ToString("F2") + "\t" + count + "\t" + estimate + "\t" + k1.Length + "\t" + k1);
            }
        }
        Console.WriteLine(count);
    }
}

public class Solver2
{
    public readonly int MAX_DIM;
    public char[] board;
    public Solver2(string prob)
    {
        board = read(prob);
        MAX_DIM = (int)Math.Sqrt(board.Length);
    }

    public string solve(int d)
    {
        string best = null;
        for (int k = d; k >= 1; k--)
        {
            string c = subSolve(k);
            if (best == null || c.Length < best.Length)
                best = c;
        }
        return best;
    }

    public string subSolve(int d)
    {
        State current = new State(copy(board), '\0', flood(board));
        var sol = "";

        while (current.score != board.Length)
        {
            State nextState = subSolve(current, d);
            sol = sol + nextState.key;
            current = nextState;
        }
        return sol;
    }

    public State subSolve(State baseState, int d)
    {
        if (d == 0)
            return baseState;
        if (!baseState.childrenGenerated)
        {
            for (int i = 0; i < baseState.children.Length; i++)
            {
                if (('1' + i) == baseState.key) continue; //no point in even eval'ing
                char[] board = copy(baseState.board);
                foreach(int idx in baseState.flood)
                    board[idx] = (char)('1' + i);
                List<int> f = flood(board);
                if (f.Count != baseState.score)
                    baseState.children[i] = new State(board, (char)('1' + i), f);
            }
            baseState.childrenGenerated = true;
        }
        State bestState = null;

        for (int i = 0; i < baseState.children.Length; i++)
            if (baseState.children[i] != null)
            {
                State bestChild = subSolve(baseState.children[i], d - 1);
                baseState.children[i].bestChildScore = bestChild.bestChildScore;
                if (bestState == null || bestState.bestChildScore < bestChild.bestChildScore)
                    bestState = baseState.children[i];
            }
        if (bestState == null || bestState.bestChildScore == baseState.score)
        {
            if (baseState.score == baseState.board.Length)
                baseState.bestChildScore = baseState.score*(d + 1);
            return baseState;
        }
        return bestState;
    }

    public char[] read(string s)
    {
        return s.Where(c => c >= '1' && c <= '6').ToArray();
    }

    public char[] copy(char[] b)
    {
        char[] n = new char[b.Length];
        for (int i = 0; i < b.Length; i++)
            n[i] = b[i];
        return n;
    }

    public List<int> flood(char[] b)
    {
        int start = (MAX_DIM * (MAX_DIM / 2)) + (MAX_DIM / 2);
        var check = new List<int>(MAX_DIM * MAX_DIM);
        bool[] seen = new bool[b.Length];
        var hits = new List<int>(MAX_DIM * MAX_DIM);

        check.Add(start);
        seen[start] = true;
        char target = b[start];

        int at = 0;
        while (at < check.Count)
        {
            int toCheck = check[at++];
            if (b[toCheck] == target)
            {
                addNeighbors(check, seen, toCheck);
                hits.Add(toCheck);
            }
        }
        return hits;
    }

    public void addNeighbors(List<int> check, bool[] seen, int loc)
    {
        //int x = loc / MAX_DIM;
        int y = loc % MAX_DIM;

        if(loc+MAX_DIM < seen.Length)
            addNeighbor(check, seen, loc+MAX_DIM);
        if(loc-MAX_DIM >= 0)
            addNeighbor(check, seen, loc-MAX_DIM);
        if(y<MAX_DIM-1)
            addNeighbor(check, seen, loc+1);
        if (y > 0)
            addNeighbor(check, seen, loc-1);
    }

    public void addNeighbor(List<int> check, bool[] seen, int l)
    {
        if (!seen[l])
        {
            seen[l] = true;
            check.Add(l);
        }
    }
}

public class State
{
    public readonly char[] board;
    public readonly char key;
    public readonly State[] children = new State[6];
    public readonly List<int> flood; 
    public readonly int score;
    public bool childrenGenerated;
    public int bestChildScore;
    public State(char[] board, char k, List<int> flood)
    {
        this.board = board;
        key = k;
        this.flood = flood;
        score = flood.Count;
        bestChildScore = score;
    }
}

Eu tentei o seu código e ele não é compilado. Há um erro próximo a uma chamada do método de solução. Além disso, também há algumas instruções "using" ausentes. De qualquer forma, se o seu programa resolver tudo em etapas de 2,1 milhões, parabéns, isso é bastante impressionante.
tigrou 5/05

@ tigrou Não tive problemas com o uso de instruções; corrigido o erro de chamada de solução - era um artefato tentar apenas atualizar o código em vez de re (copiar / colar) - nele. Desculpa aí.
CoderTao

blarg. Você quis dizer usar == importação de namespace. Corrigindo isso também.
CoderTao

Qual CPU você usa para resolver todas as placas em profundidade 5 em 11 horas? Eu executei o programa sob um I5 760@2.8Ghz. Foram necessários 30 minutos para produzir cada pedaço de 256 placas. Com base nisso, foram necessários 8 dias para resolver as 100.000 placas. A CPU estava oscilando entre 80-100% de uso durante esse tempo, todos os quatro núcleos usados. Talvez exista um problema na máquina de caixa virtual usada para executar os testes, mas isso é cerca de 16 vezes mais lento que você (você disse que demorou 11 horas).
tigrou

@tigrou Estou executando um i5 750@2.67 (hardware de 3 a 4 anos). No VS, o modo Debug vs Release é uma diferença de 50%, mas duvido que isso explique uma diferença de 16x. Se você estiver executando sob uma série linux você pode tentar compilar com mono
CoderTao

1

Delphi XE3 2.979.145 etapas

Ok, então esta é a minha tentativa. Eu chamo a parte alterada de blob, a cada turno ele fará uma cópia da matriz e testará todas as cores possíveis para ver qual cor dará a maior blob.

Executa todos os quebra-cabeças em 3 horas e 6 minutos

program Main;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  SysUtils,
  Classes,
  Generics.Collections,
  math,
  stopwatch in 'stopwatch.pas';

type
  myArr=array[0..1]of integer;
const
  MaxSize=19;
  puzLoc='here is my file';
var
  L:TList<TList<integer>>;
  puzzles:TStringList;
  sc:TList<myArr>;
  a:array[0..MaxSize-1,0..MaxSize-1] of Integer;
  aTest:array[0..MaxSize-1,0..MaxSize-1] of Integer;
  turns,midCol,sX,sY,i:integer;
  currBlob,biggestBlob,ColorBigBlob:integer;
  sTurn:string='';
  GLC:integer=0;

procedure FillArrays;
var
  ln,x,y:integer;
  puzzle:TStringList;
begin
  sc:=TList<myArr>.Create;
  puzzle:=TStringList.Create;    
  while puzzle.Count<19 do
  begin
    if puzzles[GLC]='' then
    begin
      inc(GLC);
      continue
    end
    else
      puzzle.Add(puzzles[GLC]);
    inc(GLC)
  end;    
  for y:=0to MaxSize-1do
    for x:=0to MaxSize-1do
      a[y][x]:=Ord(puzzle[y][x+1])-48;
  puzzle.Free;
end;
function CreateArr(nx,ny:integer):myArr;
begin
  Result[1]:=nx;
  Result[0]:=ny;
end;

procedure CreateBlob;
var
  tst:myArr;
  n,tx,ty:integer;
  currColor:integer;
begin
  n:=0;
  sc.Clear;
  currColor:=a[sy][sx];
  sc.Add(CreateArr(sx,sy));
  repeat
    tx:=sc[n][1];
    ty:=sc[n][0];
    if tx>0 then
      if a[ty][tx-1]=currColor then
      begin
        tst:=CreateArr(tx-1,ty);
        if not sc.Contains(tst)then
          sc.Add(tst);
      end;
    if tx<MaxSize-1 then
      if a[ty][tx+1]=currColor then
      begin
        tst:=CreateArr(tx+1,ty);
        if not sc.Contains(tst)then
          sc.Add(tst);
      end;
    if ty>0 then
      if a[ty-1][tx]=currColor then
      begin
        tst:=CreateArr(tx,ty-1);
        if not sc.Contains(tst)then
          sc.Add(tst);
      end;
    if ty<MaxSize-1 then
      if a[ty+1][tx]=currColor then
      begin
        tst:=CreateArr(tx,ty+1);
        if not sc.Contains(tst)then
          sc.Add(tst);
      end;
    inc(n);
  until (n=sc.Count);
end;

function BlobSize:integer;
var
  L:TList<myArr>;
  tst:myArr;
  n,currColor,tx,ty:integer;
begin
  n:=0;
  L:=TList<myArr>.Create;
  currColor:=aTest[sy][sx];
  L.Add(CreateArr(sx,sy));

  repeat
    tx:=L[n][1];
    ty:=L[n][0];
    if tx>0then
      if aTest[ty][tx-1]=currColor then
      begin
        tst:=CreateArr(tx-1,ty);
        if not L.Contains(tst)then
          L.Add(tst);
      end;
    if tx<MaxSize-1then
      if aTest[ty][tx+1]=currColor then
      begin
        tst:=CreateArr(tx+1,ty);
        if not L.Contains(tst)then
          L.Add(tst);
      end;
    if ty>0then
      if aTest[ty-1][tx]=currColor then
      begin
        tst:=CreateArr(tx,ty-1);
        if not L.Contains(tst)then
          L.Add(tst);
      end;
    if ty<MaxSize-1then
      if aTest[ty+1][tx]=currColor then
      begin
        tst:=CreateArr(tx,ty+1);
        if not L.Contains(tst)then
          L.Add(tst);
      end;
    inc(n);
  until n=l.Count;
  Result:=L.Count;
  L.Free;
end;

function AllsameColor(c:integer):boolean;
var
  cy,cx:integer;
begin
  Result:=true;
  for cy:=0to MaxSize-1do
    for cx:=0to MaxSize-1do
      if a[cy][cx]=c then
        continue
      else
        exit(false);
end;
procedure ChangeColors(old,new:integer; testing:boolean=false);
var
  i,j:integer;
  tst:myArr;
begin
  if testing then
  begin
    for i:= 0to MaxSize-1do
      for j:= 0to MaxSize-1do
        aTest[i][j]:=a[i][j];    
    for I:=0to sc.Count-1do
    begin
      tst:=sc[i];
      aTest[tst[0]][tst[1]]:=new;
    end;
  end
  else
  begin
    for I:=0to sc.Count-1do
    begin
      tst:=sc[i];
      a[tst[0]][tst[1]]:=new;
    end;
  end;
end;
var
  sw, swTot:TStopWatch;
  solved:integer=0;
  solutions:TStringList;
  steps:integer;
  st:TDateTime;
begin          
  st:=Now;
  writeln(FormatDateTime('hh:nn:ss',st));
  solutions:=TStringList.Create;
  puzzles:=TStringList.Create;
  puzzles.LoadFromFile(puzLoc);
  swTot:=TStopWatch.Create(true);
  turns:=0;
  repeat
    sTurn:='';    
    FillArrays;
    sX:=Round(Sqrt(MaxSize))+1;
    sY:=sX;    
    repeat
      biggestBlob:=0;
      ColorBigBlob:=0;
      midCol:=a[sy][sx];
      CreateBlob;
      for I:=1to 6do
      begin
        if I=midCol then continue;    
        ChangeColors(midCol,I,true);
        currBlob:=BlobSize;
        if currBlob>biggestBlob then
        begin
          biggestBlob:=currBlob;
          ColorBigBlob:=i;
        end;
      end;
      ChangeColors(midCol,ColorBigBlob);
      inc(turns);
      if sTurn='' then
        sTurn:=IntToStr(ColorBigBlob)
      else
        sTurn:=sTurn+', '+IntToStr(ColorBigBlob);
    until AllsameColor(a[sy][sx]);
    solutions.Add(sTurn);
    inc(solved);
    if solved mod 100=0then
      writeln(Format('Solved %d puzzles || %s',[solved,FormatDateTime('hh:nn:ss',Now-st)]));    
  until GLC>=puzzles.Count-1;    
  swTot.Stop;
  WriteLn(Format('solving these puzzles took %d',[swTot.Elapsed]));
  writeln(Format('Total moves: %d',[turns]));
  solutions.SaveToFile('save solutions here');
  readln;
end.

Pensando em um método backtracing de força bruta também.
Talvez divertido para este fim de semana ^^


0

Javascript / node.js - 2.588.847

O algoritmo é um pouco diferente da maioria aqui, pois utiliza regiões pré-calculadas e estados de diferença entre os cálculos. É executado em menos de 10 minutos aqui se você estiver preocupado com a velocidade por causa do javascript.

var fs = require('fs')


var file = fs.readFileSync('floodtest','utf8');
var boards = file.split('\n\n');
var linelength  = boards[0].split('\n')[0].length;
var maxdim = linelength* linelength;


var board = function(info){
    this.info =[];
    this.sameNeighbors = [];
    this.differentNeighbors = [];
    this.samedifferentNeighbors = [];
    for (var i = 0;i <info.length;i++ ){
        this.info.push(info[i]|0);
    };

    this.getSameAndDifferentNeighbors();
}

board.prototype.getSameAndDifferentNeighbors = function(){
    var self = this;
    var info = self.info;
    function getSameNeighbors(i,value,sameneighbors,diffneighbors){

        var neighbors = self.getNeighbors(i);
        for(var j =0,nl = neighbors.length; j< nl;j++){
            var index = neighbors[j];
            if (info[index]  === value ){
                if( sameneighbors.indexOf(index) === -1){
                    sameneighbors.push(index);
                    getSameNeighbors(index,value,sameneighbors,diffneighbors);
                }
            }else if( diffneighbors.indexOf(index) === -1){
                    diffneighbors.push(index);
            }
        } 

    }


    var sneighbors = [];
    var dneighbors = [];
    var sdneighbors = [];

    for(var i= 0,l= maxdim;i<l;i++){
        if (sneighbors[i] === undefined){
            var sameneighbors = [i];
            var diffneighbors = [];
            getSameNeighbors(i,info[i],sameneighbors,diffneighbors);
            for (var j = 0; j<sameneighbors.length;j++){
                var k = sameneighbors[j];
                sneighbors[k] = sameneighbors;
                dneighbors[k] = diffneighbors;
            } 
        }

    }

    for(var i= 0,l= maxdim;i<l;i++){
        if (sdneighbors[i] === undefined){
            var value = [];
            var dni = dneighbors[i];
            for (var j = 0,dnil = dni.length; j<dnil;j++){
                var dnij = dni[j];
                var sdnij = sneighbors[dnij];
                for(var k = 0,sdnijl = sdnij.length;k<sdnijl;k++){
                    if (value.indexOf(sdnij[k])=== -1){
                        value.push(sdnij[k]);
                    }
                }
            };
            var sni = sneighbors[i];
            for (var j=0,snil = sni.length;j<snil;j++){
                sdneighbors[sni[j]] = value;
            };
        };
    }
    this.sameNeighbors = sneighbors;
    this.differentNeighbors =  dneighbors;
    this.samedifferentNeighbors =sdneighbors;

}

board.prototype.getNeighbors = function(i){
        var returnValue = [];

        var index = i-linelength;
        if (index >= 0){
            returnValue.push(index);
        }

        index = i+linelength;
        if (index < maxdim){

            returnValue.push(index);
        }

        index = i-1;

        if (index >= 0 && index/linelength >>> 0 === i/linelength  >>> 0){
            returnValue.push(index);
        }
        index = i+1;
        if (index/linelength >>> 0 === i/linelength >>> 0){
            returnValue.push(index);
        }

        if (returnValue.indexOf(-1) !== -1){
            console.log(i,parseInt(index/linelength,10),parseInt(i/linelength,10));
        } 
        return returnValue 
}

board.prototype.solve = function(){
    var i,j;
    var info = this.info;
    var sameNeighbors = this.sameNeighbors;
    var samedifferentNeighbors = this.samedifferentNeighbors;
    var middle = 9*19+9;
    var maxValues = [];

    var done = {};
    for (i=0; i<sameNeighbors[middle].length;i++){
        done[sameNeighbors[middle][i]] = true;
    }
    var usefullNeighbors = [[],[],[],[],[],[],[]];
    var diff = [];
    var count = [0];

    count[1] = 0;
    count[2] = 0;
    count[3] = 0;
    count[4] = 0;
    count[5] = 0;
    count[6] = 0;

    var addusefullNeighbors = function(index,diff){

        var indexsamedifferentNeighbors =samedifferentNeighbors[index];
        for (var i=0;i < indexsamedifferentNeighbors.length;i++){
            var is = indexsamedifferentNeighbors[i];
            var value = info[is];
            if (done[is] === undefined && usefullNeighbors[value].indexOf(is) === -1){
                usefullNeighbors[value].push(is);
                diff.push(value);
            }

        }
    }
    addusefullNeighbors(middle,diff);


    while(  usefullNeighbors[1].length > 0 || usefullNeighbors[2].length > 0 ||
            usefullNeighbors[3].length > 0 || usefullNeighbors[4].length > 0 ||
            usefullNeighbors[5].length > 0 || usefullNeighbors[6].length > 0 ){
        for (i=0;i < diff.length;i++){ 
            count[diff[i]]++;
        };
        var maxValue = count.indexOf(Math.max.apply(null, count));
        diff.length = 0;
        var used = usefullNeighbors[maxValue];
        for (var i=0,ul = used.length;i < ul;i++){
            var index = used[i];
            if (info[index] === maxValue){
                done[index] = true;
                addusefullNeighbors(index,diff);
            }
        }
        used.length = 0;
        count[maxValue] = 0;


        maxValues.push(maxValue);
    }
    return maxValues.join("");
};
var solved = [];
var start = Date.now();
for (var boardindex =0;boardindex < boards.length;boardindex++){ 
    var b = boards[boardindex].replace(/\n/g,'').split('');
    var board2 = new board(b);
    solved.push(board2.solve());
};
var diff = Date.now()-start;
console.log(diff,boards.length);
console.log(solved.join('').length);
console.log("end");

fs.writeFileSync('solution.txt',solved.join('\n'),'utf8');

-3

Código C que é garantido para encontrar uma solução ideal por força bruta simples. Funciona para grades de tamanho arbitrário e todas as entradas. Leva muito, muito tempo para ser executado na maioria das grades.

O preenchimento é extremamente ineficiente e depende de recursão. Pode ser necessário aumentar sua pilha se for muito pequena. O sistema de força bruta usa uma string para armazenar os números e um add-to-carry simples para percorrer todas as opções possíveis. Isso também é extremamente ineficiente, pois repete a maioria dos passos quatrilhões de vezes.

Infelizmente, não pude testá-lo em todos os casos, pois vou morrer de velhice antes que termine.

#include <stdio.h>
#include <string.h>


#define GRID_SIZE       19

char grid[GRID_SIZE][GRID_SIZE] = { {3,3,5,4,1,3,4,1,5,3,3,5,4,1,3,4,1,5},
                                    {5,1,3,4,1,1,5,2,1,3,3,5,4,1,3,4,1,5},
                                    {6,5,2,3,4,3,3,4,3,3,3,5,4,1,3,4,1,5},
                                    {4,4,4,5,5,5,4,1,4,3,3,5,4,1,3,4,1,5},
                                    {6,2,5,3,3,1,1,6,6,3,3,5,4,1,3,4,1,5},
                                    {5,5,1,2,5,2,6,6,3,3,3,5,4,1,3,4,1,5},
                                    {6,1,1,5,3,6,2,3,6,3,3,5,4,1,3,4,1,5},
                                    {1,2,2,4,5,3,5,1,2,3,3,5,4,1,3,4,1,5},
                                    {3,6,6,1,5,1,3,2,4,3,3,5,4,1,3,4,1,5} };
char grid_save[GRID_SIZE][GRID_SIZE];

char test_grids[6][GRID_SIZE][GRID_SIZE];

void flood_fill(char x, char y, char old_colour, char new_colour)
{
    if (grid[y][x] == new_colour)
        return;

    grid[y][x] = new_colour;

    if (y > 0)
    {
        if (grid[y-1][x] == old_colour)
            flood_fill(x, y-1, old_colour, new_colour);
    }
    if (y < GRID_SIZE - 1)
    {
        if (grid[y+1][x] == old_colour)
            flood_fill(x, y+1, old_colour, new_colour);
    }

    if (x > 0)
    {
        if (grid[y][x-1] == old_colour)
            flood_fill(x-1, y, old_colour, new_colour);
    }
    if (x < GRID_SIZE - 1)
    {
        if (grid[y][x+1] == old_colour)
            flood_fill(x+1, y, old_colour, new_colour);
    }
}

bool check_grid(void)
{
    for (char i = 0; i < 6; i++)
    {
        if (!memcmp(grid, &test_grids[i][0][0], sizeof(grid)))
            return(true);
    }

    return(false);
}

void inc_string_num(char *s)
{
    char *c;

    c = s + strlen(s) - 1;
    *c += 1;

    // carry
    while (*c > '6')
    {
        *c = '1';
        if (c == s) // first char
        {
            strcat(s, "1");
            return;
        }
        c--;
        *c += 1;
    }
}

void print_grid(void)
{
    char x, y;
    for (y = 0; y < GRID_SIZE; y++)
    {
        for (x = 0; x < GRID_SIZE; x++)
            printf("%d ", grid[y][x]);
        printf("\n");
    }
    printf("\n");
}

int main(int argc, char* argv[])
{
    // create test grids for comparisons
    for (char i = 0; i < 6; i++)
        memset(&test_grids[i][0][0], i+1, GRID_SIZE*GRID_SIZE);

    char s[256] = "0";
    //char s[256] = "123456123456123455";
    memcpy(grid_save, grid, sizeof(grid));


    print_grid();
    do
    {
        memcpy(grid, grid_save, sizeof(grid));
        inc_string_num(s);

        for (unsigned int i = 0; i < strlen(s); i++)
        {
            flood_fill(4, 4, grid[4][4], s[i] - '0');
        }
    } while(!check_grid());
    print_grid();

    printf("%s\n", s);

    return 0;
}

Tanto quanto posso dizer, este é o atual vencedor. A competição exige que:

Seu programa deve ser totalmente determinístico; Soluções pseudo-aleatórias são permitidas, mas o programa deve gerar sempre a mesma saída para o mesmo caso de teste.

Verifica

O programa vencedor executará o menor número total de etapas para resolver todos os 100.000 casos de teste encontrados neste arquivo (arquivo de texto compactado, 14,23 MB). Se duas soluções seguirem o mesmo número de etapas (por exemplo, se ambas encontraram a estratégia ideal), o programa mais curto vencerá.

Como sempre encontra o menor número de etapas para concluir todos os quadros e nenhum dos outros, atualmente está à frente. Se alguém puder criar um programa mais curto, poderá ganhar, então apresento a seguinte versão otimizada para o tamanho. A execução é um pouco mais lenta, mas o tempo de execução não faz parte das condições vencedoras:

#include <stdio.h>
#include <string.h>
#define A 9
int g[A][A]={{3,3,5,4,1,3,4,1,5},{5,1,3,4,1,1,5,2,1},{6,5,2,3,4,3,3,4,3},{4,4,4,5,5,5,4,1,4},{6,2,5,3,3,1,1,6,6},{5,5,1,2,5,2,6,6,3},{6,1,1,5,3,6,2,3,6},{1,2,2,4,5,3,5,1,2},{3,6,6,1,5,1,3,2,4}};
int s[A][A];
int t[6][A][A];
void ff(int x,int y,int o,int n)
{if (g[y][x]==n)return;g[y][x]=n;if (y>0){if(g[y-1][x]==o)ff(x,y-1,o,n);}if(y<A-1){if(g[y+1][x]==o)ff(x,y+1,o,n);}if(x>0){if (g[y][x-1] == o)ff(x-1,y,o,n);}if(x<A-1){if(g[y][x+1]==o)ff(x+1,y,o,n);}}
bool check_g(void)
{for(int i=0;i<6;i++){if(!memcmp(g,&t[i][0][0],sizeof(g)))return(true);}return(0);}
void is(char*s){char*c;c=s+strlen(s)-1;*c+=1;while(*c>'6'){*c='1';if (c==s){strcat(s,"1");return;}c--;*c+=1;}}
void pr(void)
{int x, y;for(y=0;y<A;y++){for(x=0;x<A;x++)printf("%d ",g[y][x]);printf("\n");}printf("\n");}
int main(void)
{for(int i=0;i<6;i++)memset(&t[i][0][0],i+1,A*A);char s[256]="0";memcpy(s,g,sizeof(g));pr();do{memcpy(g,s,sizeof(g));is(s);for(int i=0;i<strlen(s);i++){ff(4,4,g[4][4],s[i]-'0');}}while(!check_g());
pr();printf("%s\n",s);return 0;}

Até o momento, é a única entrada que obtém sempre a solução ideal. Eu diria que também é uma solução de referência de último lugar melhor. Na verdade, não estou convencido de que exista realmente uma maneira melhor de garantir uma solução ideal em todos os casos, e até agora ninguém mais provou o contrário.
usuário

1
Até que você possa realmente encontrar o número exato de etapas necessárias, não posso aceitar essa solução, mesmo que seja (teoricamente) a melhor.
Joe Z.

Além disso, o tamanho da grelha é de 19, não 9.
Joe Z.

Ok, eu corrigi o tamanho da grade. Alguém sabe como calcular o número mínimo teórico de etapas necessárias?
usuário

Não. Você precisaria usar um programa para resolvê-lo, que é o que você tem agora.
Joe Z.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.