Créer une AI de peinture d'inondation


34

Dans le jeu Flood Paint, le but du jeu est de faire en sorte que le tableau entier soit de la même couleur en aussi peu de tours que possible.

Le jeu commence avec un tableau ressemblant à ceci:

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

Actuellement, le nombre (représentant une couleur) au centre du tableau est 3. Chaque tour, le carré au centre change de couleur et tous les carrés de la même couleur accessibles depuis le centre en se déplaçant horizontalement ou verticalement ( c'est-à-dire dans la zone inondée du carré central) changera de couleur avec elle. Donc, si le carré du centre change de couleur en 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

alors le 3 qui était à gauche du centre 3 changera également de couleur. Maintenant, il y a un total de sept 5 accessibles depuis le centre, et donc si nous changeons la couleur en 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

la région peinte augmente de nouveau considérablement.

Votre tâche consiste à créer un programme prenant comme entrée une grille de couleurs 19-sur-19, sous la forme de votre choix:

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

et retourne une séquence de couleurs que le carré central va changer à chaque tour, toujours dans le format de votre choix:

263142421236425431645152623645465646213545631465

À la fin de chaque séquence de mouvements, les carrés de la grille 19 x 19 doivent tous être de la même couleur.

Votre programme doit être entièrement déterministe; Les solutions pseudo-aléatoires sont autorisées, mais le programme doit générer la même sortie pour le même scénario de test à chaque fois.

Le programme gagnant prendra le moins de mesures possible pour résoudre les 100 000 tests élémentaires contenus dans ce fichier (fichier texte compressé, 14,23 Mo). Si deux solutions prennent le même nombre d'étapes (par exemple, si les deux ont trouvé la stratégie optimale), le programme le plus court l'emportera.


BurntPizza a écrit un programme en Java pour vérifier les résultats du test. Pour utiliser ce programme, lancez votre soumission et dirigez la sortie vers un fichier appelé steps.txt. Ensuite, exécutez ce programme avec steps.txtet le floodtestfichier dans le même répertoire. Si votre entrée est valide et produit des solutions correctes pour tous les fichiers, elle doit réussir tous les tests et renvoyerAll 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.");
    }
}

En outre, un tableau de bord, puisque les résultats ne sont pas réellement triés par score et ici, cela compte beaucoup:

  1. 1 985 078 - smack42, Java
  2. 2 075 452 - utilisateur1502040, 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 octets)
  12. 2 480 714 - Herjan, Java (4 702 octets)
  13. 2 588 847 - BurntPizza, Java (2 748 octets)
  14. 2 588 847 - Gero3, node.js (4 641 octets)
  15. 2 979 145 - Teun Pronk, Delphi XE3
  16. 4 780 841 - BurntPizza, Java
  17. 10 800 000 - Joe Z., Python

2
À en juger par votre propre soumission, la sortie ne devrait pas contenir d'espaces?
Martin Ender

5
Il est à noter que les données d'entrée de test ne comportent pas d'espaces entre les nombres.
nderscore

3
Vous pouvez toujours l'écrire. Si cela réduit le gagnant actuel, je changerai la réponse acceptée.
Joe Z.

4
La contrainte de temps est "il doit être assez rapide pour que vous puissiez l'exécuter et afficher les résultats réels ici".
Joe Z.

2
@AlexanderRevo Je pensais que je n'avais pas déplacé le fichier, mais apparemment, le lien est en place et a changé sans que je le fasse. Voici le lien à nouveau.
Joe Z.

Réponses:


4

Java - 1 985 078 étapes

https://github.com/smack42/ColorFill

Une autre entrée tardive. Le fichier de résultat contenant les 1 985 078 étapes peut être trouvé ici .

Quelques informations de fond:

J'ai découvert ce défi il y a quelques années, lorsque j'ai commencé à programmer mon propre clone du jeu Flood-it.

Algorithmes DFS et A * "best-of incomplets"
Depuis le début, je voulais créer un bon algorithme de résolution pour ce jeu. Au fil du temps, je pouvais améliorer mon solveur en incluant plusieurs stratégies faisant différentes recherches incomplètes (similaires à celles utilisées dans les autres programmes ici) et en utilisant le meilleur résultat de ces stratégies pour chaque solution. J'ai même ré-implémenté l'algorithme A * de tigrou en Java et je l'ai ajouté à mon solveur pour obtenir des solutions globalement meilleures que le résultat de tigrou.

algorithme DFS exhaustif
Ensuite, je me suis concentré sur un algorithme qui trouve toujours les solutions optimales. J'ai déployé beaucoup d'efforts pour optimiser ma stratégie exhaustive de recherche en profondeur d'abord. Pour accélérer la recherche, j'ai inclus une table de hachage qui stocke tous les états explorés, afin que la recherche évite de les explorer à nouveau. Bien que cet algorithme fonctionne correctement et résolve tous les énigmes 14x14 assez rapidement, il utilise trop de mémoire et s'exécute très lentement avec les énigmes 19x19 de ce défi de code.

Algorithme Puchert A *
Il y a quelques mois, j'ai été contacté par Aaron et Simon Puchert pour examiner le solveur Flood-It . Ce programme utilise un algorithme de type A * avec une heuristique admissible (contrairement à celle de tigrou) et déplace l'élagage de la même manière que la recherche de points de saut. J'ai vite remarqué que ce programme est très rapide et trouve les solutions optimales !

Bien sûr, j'ai dû ré-implémenter cet excellent algorithme et l'a ajouté à mon programme. Je me suis efforcé d’optimiser mon programme Java pour qu’il fonctionne à peu près aussi vite que le programme C ++ original des frères Puchert. Ensuite, j'ai décidé d'essayer les 100 000 tests élémentaires de ce défi. Sur ma machine, le programme a fonctionné pendant plus de 120 heures pour trouver les 1 985 078 étapes, en utilisant l’implémentation de l’ algorithme Puchert A * .

C’est la meilleure solution possible à ce problème, à moins que le programme ne contienne que quelques bugs qui conduiraient à des solutions sous-optimales. Tous les commentaires sont les bienvenus!

edit: ajouté les parties pertinentes du code ici:

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

}

partie de la 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;
    }
}

partie de la 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
Bienvenue chez PPCG! Pourriez-vous inclure le code approprié pour le solutionneur dans la réponse elle-même, de manière à ce qu'il soit autonome, si votre dépôt github devait être déplacé ou baissé?
Martin Ender

J'ai ajouté ici les parties les plus pertinentes du code: mon implémentation du "Puchert A * algorithm". (Cet extrait de code n'est pas autonome et ne peut pas être compilé tel
quel

Je suis heureux que quelqu'un ait trouvé une solution parfaite / optimale pour cela. Mais de l'autre côté, cela signifie qu'il n'y aura plus de concurrence / de nouvelles réponses.
Tigrou

15

C # - 2 098 382 étapes

J'ai essayé beaucoup de choses, la plupart ont échoué et n'ont tout simplement pas fonctionné, jusqu'à récemment. J'ai quelque chose d'intéressant à poster une réponse.

Il existe certainement des moyens d'améliorer encore cette situation. Je pense que passer sous les étapes 2M pourrait être possible.

Il a fallu environ 7 hourspour générer des résultats. Voici un fichier txt avec toutes les solutions, au cas où quelqu'un voudrait les vérifier: 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));                                             
                    }       
                }               
            }
        }
    }
}

Plus de détails sur son fonctionnement:

Il utilise l' algorithme A * Pathfinding .

Ce qui est difficile, c'est de trouver un bien heuristic. Si le heuristiccoût est sous-estimé, il fonctionnera presque comme l' algorithme de Dijkstra et, en raison de la complexité d'une carte 19x19 à 6 couleurs, il fonctionnera à l'infini. Si elle surestime le coût, elle convergera rapidement vers une solution mais ne donnera pas de bonne solution (quelque chose comme 26 coups contre 19 était possible). Trouver ce heuristicqu'il y a de mieux qui donne exactement le nombre restant d'étapes pour trouver la solution serait le meilleur (ce serait rapide et donnerait la meilleure solution possible). C'est (à moins que je me trompe) impossible. En fait, il faut d'abord résoudre le tableau lui-même, alors que c'est ce que vous essayez réellement de faire (problème de la poule et de l'œuf)

J'ai essayé beaucoup de choses, voici ce qui a fonctionné pour le heuristic:

  • Je construis un graphique du conseil actuel à évaluer. Chacun nodereprésente un ensemble de carrés de même couleur contigus. Grâce à cela graph, je peux facilement calculer la distance minimale exacte entre le centre et tout autre nœud. Par exemple, la distance entre le centre et le coin supérieur gauche serait de 10, car au moins 10 couleurs les séparent.
  • Pour le calcul heuristic: je joue jusqu'au tableau actuel. Pour chaque étape, je choisis la couleur qui minimisera la somme des distances entre la racine et tous les autres nœuds.
  • Le nombre de mouvements nécessaires pour atteindre ce but est le heuristic.

  • Estimated cost(utilisé par A *) = moves so far+heuristic

Ce n’est pas parfait car il surestime légèrement le coût (on trouve donc une solution non optimale). Quoi qu'il en soit, il est rapide de calculer et de donner de bons résultats.

J'ai pu obtenir une amélioration considérable de la vitesse en utilisant un graphique pour effectuer toutes les opérations. Au début j'avais un 2-dimensiontableau. Je l'inonde et recalcule le graphe si nécessaire (par exemple: pour calculer l'heuristique). Maintenant, tout est fait en utilisant le graphique, qui est calculé uniquement au début. Pour simuler des étapes, l'inondation n'est plus nécessaire, je fusionne plutôt des nœuds. C'est beaucoup plus rapide.


2
S'il vous plaît ne pas utiliser code blockspour mettre en valeur le texte. Nous avons italique et gras pour cela.
La poursuite de Monica

10

Python - 10 800 000 pas

En tant que solution de référence de dernière place, considérons cette séquence:

print "123456" * 18

En parcourant tous les ntemps de couleurs, nvous garantissez que chaque pas carré sera de la même couleur que le carré central. Chaque carré est au plus à 18 pas du centre, ainsi 18 cycles garantissent que tous les carrés ont la même couleur. Très probablement, il se terminera dans moins que cela, mais le programme n'est pas obligé de s'arrêter dès que tous les carrés sont de la même couleur; c'est juste beaucoup plus bénéfique de le faire. Cette procédure constante compte 108 étapes par test élémentaire, pour un total de 10 800 000.


Méthode de la force brute, sérieusement ...? Joe, je pensais que tu avais un peu plus d'expérience pour mieux connaître, mec?
WallyWest

2
Ce n'est pas conçu comme une entrée sérieuse. Notez que je le présente spécifiquement comme une solution pour agir comme un fourre-tout à la dernière place . Toute entrée sérieuse aurait un score bien inférieur à celui-ci.
Joe Z.

Ne devrait-il pas y avoir des espaces? Comme dans 1 2 3 4 5 6 ...au lieu de votre solution actuelle qui donne 123456....
user80551

1
Serait l'algorithme optimal pour le code de golf (dans une autre langue bien que "print" soit trop de caractères).
Cruncher

1
Je ne pense pas non plus que le pire des 18 marches soit possible . Mais bien sûr, nous savons qu’il n’ya pas d’affaire pire que celle-là, cela fonctionne donc
parfaitement

8

Java - 2 480 714 étapes

J'ai fait une petite erreur avant (j'ai mis une phrase cruciale avant une boucle au lieu de la boucle:

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

Combien de temps cela a-t-il pris pour courir?
alexander-brett

@ ali0sha Mon pc n'a pas pris même une demi-minute
Herjan

Bien merde. La mienne est en marche depuis une demi-heure ...
alexander-brett

Le golf n'est pas obligatoire, en passant.
Joe Z.

1
@ m.buettner Parlez du diable, quelqu'un a lié cette solution (et avait un code plus court) trois heures après que vous ayez dit cela.
Joe Z.

5

C - 2.075.452

Je sais que je suis extrêmement en retard pour la fête, mais j'ai vu ce défi et je voulais tenter ma chance.

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

L'algorithme est basé sur une recherche d'arborescence Monte-Carlo avec échantillonnage Thompson et sur un tableau de transposition pour réduire l'espace de recherche. Cela prend environ 12 heures sur ma machine. Si vous souhaitez vérifier les résultats, vous pouvez les trouver à l' adresse https://dropfile.to/pvjYDMV .


L' utilisateur smack42 suggère de changer hash ^= zobrist_table[i][(int)solution[i]];pour hash ^= zobrist_table[i%len][(int)solution[i]];de plantage du programme correctif.
Stephen

@StepHen Je ne vois pas comment un score peut être supérieur à len. Avez-vous une entrée qui fait ce crash? Avez-vous un lien vers votre conversation avec smak42? Même si je ne peux pas planter, je suppose qu'il n'y a pas de mal à être du côté sûr avec un code non performant.
user1502040

Non, désolé, c'était dans les modifications suggérées. Voici le commentaire: codegolf.stackexchange.com/review/suggested-edits/42008
Stephen

+1 pour me battre à ce sujet. Mais attention, il y aura peut-être des améliorations à venir;)
tigrou

4

Java - 2 434 108 2 588 847 étapes

En train de gagner (~ 46K devant Herjan) à partir du 4/26

Welp, non seulement MrBackend m'a battu, mais j'ai aussi trouvé un bug qui produisait un score trompeusement bon. C'est corrigé maintenant (c'était aussi dans le vérificateur! Ack), mais malheureusement, je n'ai pas le temps pour le moment d'essayer de récupérer la couronne. Je vais essayer plus tard.

Ceci est basé sur mon autre solution, mais au lieu de peindre avec la couleur la plus commune aux bords de remplissage, il peint avec la couleur qui va exposer les bords qui ont beaucoup de carrés adjacents de la même couleur. Appelez ça LookAheadPainter. Je vais jouer au golf plus tard si nécessaire.

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: J'ai écrit un vérificateur, n'hésitez pas à l'utiliser, il attend un fichier steps.txt contenant les étapes que votre programme génère ainsi que le fichier floodtest: Edit-Edit: (voir OP)

Si quelqu'un trouve un problème, merci de me le signaler!


Nice, Pizza! Et ce vérificateur est vraiment intelligent! Le PO aurait dû créer quelque chose comme ceci / un programme de contrôle (qui aurait résolu beaucoup de problèmes).
Herjan

3

C - 2 480 714 étapes

Pas encore optimal, mais il est maintenant plus rapide et obtient de meilleurs résultats.

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

Bien joué Willem, merci de me mentionner dans votre description. Je suis honoré par votre grâce.
Herjan

Pas de problème, cher Herjan
SteelTermite

Soit dit en passant, votre affirmation selon laquelle "les résultats sont légèrement meilleurs que ceux de Herjan" est déjà dépassée. Je viens d'appliquer l'amélioration dont j'ai parlé (par la poste);) Bonne chance de me battre maintenant!
Herjan

1
515 pas devant vous, jamais entendu parler de l'ajout / suppression d'un '=', à titre comparatif, heheh
Herjan

En effet, Herjan. Je mettrai à jour ma soumission selon votre suggestion.
SteelTermite

3

Java - 2 245 529 2 201 995 étapes

Recherche parallèle et en cache dans la profondeur 5, minimisant le nombre d'îlots. Puisque l'amélioration de la profondeur 4 à la profondeur 5 était si faible, je ne pense pas qu'il soit vraiment utile de l'améliorer davantage. Mais si cela devait être amélioré, mon instinct me dit de travailler avec le calcul du nombre d’îles comme un différentiel entre deux États, au lieu de tout recalculer.

Présente actuellement les sorties sur stdout, jusqu'à ce que je connaisse le format d'entrée du vérificateur.

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

}

Impressionnant, pouvez-vous lui faire écrire les étapes dans un fichier? Pour que nous puissions le vérifier?
Herjan

@ Herjan, il semble que son code soit auto-validant. Voir isSolved ()
BurntPizza

@ BurntPizza Alors? Mon code est également auto-validant, lol ... Je veux dire, cela peut être tout aussi faux que mon propre code.
Herjan

isSolved () n'est pas pour la validation, c'est pour la terminaison. Quant à écrire - fera dans la prochaine version.
MrBackend

Je serais intéressé si une heuristique qui permettait de rechercher 5 étapes en profondeur uniquement si le nombre d'étapes trouvé pour 4 était supérieur à celui 24résultant d'une exécution beaucoup plus efficace.
Joe Z.

2

Ma dernière entrée: C - 2 384 020 étapes

Cette fois, il s'agit d'un contrôle de toutes les possibilités ... Ce score est obtenu avec la profondeur définie sur 3. La profondeur à 5 devrait donner ~ 2,1 millions d'étapes ... TROP LENTE. Profondeur 20+ donne le moins de pas possible (il vérifie simplement tous les matches et les victoires les plus courtes du parcours) ... Il comporte le moins de pas possible, même si je le déteste, car il est un peu meilleur, mais les performances sont nulles. Je préfère mon autre entrée C, qui est également dans ce 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;
}

Une autre IA améliorée écrite en C - 2 445 761 étapes

Basé sur 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;
}

... et ~ 200K pour battre le mien;)
MrBackend

Vous devriez poster chaque entrée en tant que réponse individuelle.
Joe Z.

@ JoeZ. Désolé, mais cela ressemblait à du spam, alors j'ai décidé de les assembler en une seule réponse (peu importe, car seul le meilleur (le meilleur = l'IA avec le plus petit nombre de pas) compte). Au moins c'est ce que je pensais.
Herjan

1

Java - 2 610 797 4 780 841 étapes

(Fill-Bug corrigé, le score est maintenant bien pire -_-)

Ceci est ma soumission d'algorithme de référence de base, crée simplement un histogramme des carrés sur les bords de la zone peinte et peint avec la couleur la plus commune. Exécute le 100k en quelques minutes.

Évidemment, il ne gagnera pas, mais ce n’est certainement pas le dernier. Je ferai probablement une autre soumission pour des choses intelligentes. N'hésitez pas à utiliser cet algorithme comme point de départ.

Décommentez les lignes commentées pour la sortie complète. Par défaut, le nombre d'étapes prises est imprimé.

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

Golfs à 860 caractères (sans compter les nouvelles lignes pour le formatage), mais pourrait être réduit davantage si j'avais envie d'essayer:

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

La seule raison pour laquelle ce n'est "certainement pas le dernier" est parce que ma solution de référence est là pour résoudre le problème. Il s’agit en fait de la dernière place parmi toutes les candidatures présentées par d’autres personnes pour le moment: P
Joe Z.

@ JoeZ. C'était devant SteelTermite, mais il a amélioré la sienne. Je pensais que c'était la "prochaine étape logique de l'approche naïve". Je serais inquiet s'il se portait bien
BurntPizza

1

Haskell - 2 475 056 étapes

L'algorithme est similaire à celui suggéré par MrBackend dans les commentaires. La différence est la suivante: sa suggestion trouve le chemin le moins cher vers le carré de coût le plus élevé, le mien réduit avec avidité l’excentricité du graphique à chaque étape.

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

At-il fini de courir?
Joe Z.

Pas encore, il aurait peut-être fini à présent si je l'avais laissé tourner du jour au lendemain, mais le ventilateur était bruyant et j'ai donc hiberné l'ordinateur. Il fonctionne à nouveau maintenant, vérifiera à nouveau quand je rentrerai du travail.
Jeremy List

Il s'est écrasé à cause d'un débordement de pile, en modifiant maintenant pour éviter cela.
Jeremy List

1

C # - 2.383.569

C'est un parcours en profondeur de solutions possibles qui choisit grossièrement la voie de la meilleure amélioration (similaire / identique à l'entrée C de Herjan), sauf que j'ai astucieusement inversé l'ordre de génération de solutions candidates après avoir vu Herjan afficher les mêmes chiffres. Prend bien plus de 12 heures à courir si.

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)

C'était supposé être ma tentative de force brute. Mais! Ma première implémentation du "meilleur" choix de profondeur unique a donné:

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

Le code utilisé pour les deux est identique, la force brute stockant un "instantané" des autres mouvements possibles et exécutant l'algorithme sur chacun d'eux.


  • Problèmes

Si vous utilisez l'approche "multi" passes, des échecs aléatoires se produiront. J'ai configuré les 100 premières entrées de puzzle dans un test unitaire et je peux réussir à 100%, mais pas à 100% du temps. Pour compenser, j'ai juste suivi le numéro du casse-tête actuel au moment de l'échec et commencé un nouveau thread en ramassant où le dernier s'est arrêté. Chaque fil a écrit leurs résultats respectifs dans un fichier. Le pool de fichiers a ensuite été condensé en un seul fichier.

  • Approche

Nodereprésente une tuile / un carré du tableau et stocke une référence à tous ses voisins. Suivre trois Set<Node>variables Remaining, Painted, Targets. Chaque itération cherche à Targetsregrouper tous les candidatenœuds par valeur, en sélectionnanttarget value le nombre de nœuds "affectés". Ces nœuds affectés deviennent alors les cibles de la prochaine itération.

La source est répartie dans de nombreuses classes et les extraits ne sont pas très significatifs en dehors du contexte global. Ma source peut être parcourue via GitHub . J'ai aussi joué avec une démonstration de JSFiddle pour la visualisation.

Néanmoins, ma méthode cheval de bataille 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

C’est effectivement la même approche de recherche du meilleur descendant que mon autre solutionneur, mais avec quelques optimisations qui permettent à peine, avec un parallélisme, d’aller à la profondeur 5 en un peu moins de 10 heures. En testant cela, j'ai aussi trouvé un bogue dans l'original, de telle sorte que l'algorithme prenait parfois des routes inefficaces vers l'état final (il ne tenait pas compte de la profondeur d'états avec score = 64; découvert en jouant avec des résultats de profondeur = 7).

La principale différence entre ce résolveur et le solveur précédent est qu’il garde les états du jeu Flood en mémoire afin qu’il n’ait pas à régénérer 6 ^ 5 états. En fonction de l’utilisation du processeur en cours d’exécution, je suis à peu près certain que cela est passé du processeur lié à la bande passante mémoire. Très amusant. Tant de péchés.

Edit: pour des raisons, l’algorithme de profondeur 5 ne produit pas toujours le meilleur résultat. Pour améliorer les performances, prenons la profondeur 5 ... et 4 ... et 3 et 2 et 1 et voyons quelle est la meilleure solution. Est-ce que nous avons encore rasé 40k mouvements? Étant donné que la profondeur 5 représente la majeure partie du temps, l’ajout de 4 à 1 ne fait qu'augmenter la durée d’exécution de ~ 10 heures à ~ 11 heures. 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;
    }
}

J'ai essayé votre code et il ne compile pas. Il y a une erreur près d'un appel de méthode de résolution. A côté de cela, il manque également quelques déclarations "using". Quoi qu'il en soit, si votre programme résout tout en étapes 2.1M, félicitations, c'est plutôt impressionnant.
Tigrou

@tigrou Je n'ai eu aucun problème avec l'utilisation des déclarations; correction de l'erreur d'appel de résolution: il s'agissait d'un artefact consistant à simplement mettre à jour le code au lieu de le recopier / coller. Désolé pour ça.
CoderTao

blarg. Vous vouliez utiliser == importation d'espace de noms. Résoudre cela aussi.
CoderTao

Quel processeur utilisez-vous pour résoudre toutes les cartes en profondeur 5 en 11 heures? J'ai exécuté le programme sous un I5 760@2.8Ghz. Il a fallu 30 minutes pour produire chaque morceau de 256 cartes. Sur cette base, il aurait fallu 8 jours pour résoudre les 100 000 planches. Le processeur rebondissait entre 80% et 100% d’utilisation pendant cette période, les quatre cœurs utilisés. Peut-être y at-il un problème que la machine virtualbox utilisait pour exécuter les tests, mais qui est environ 16 fois plus lente que vous (vous avez dit que cela prenait 11 heures).
Tigrou

@tigrou J'utilise un i5 750@2.67 (matériel âgé de 3 à 4 ans). Sous VS, le mode Debug vs Release est une différence de 50%, mais je doute que cela expliquerait une différence de 16x. Si vous utilisez un hôte Linux, essayez de compiler avec mono
CoderTao

1

Delphi XE3 2 979 145 étapes

Ok, c'est ma tentative. J'appelle la partie changeante une goutte, à chaque tour, il en fera une copie et testera toutes les couleurs possibles pour voir quelle couleur donnera la plus grosse goutte.

Exécute tous les puzzles en 3 heures et 6 minutes

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.

Pensez aussi à une méthode de traçage bruteforce.
Peut-être amusant pour ce week-end ^^


0

Javascript / node.js - 2 588 847

L'algorithme est un peu différent de la plupart des logiciels ici, car il utilise des régions précalculées et des états différents entre les calculs. Si vous êtes inquiet pour la vitesse à cause de javascript, il dure moins de 10 minutes.

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 code qui est garanti pour trouver une solution optimale par simple force brute. Fonctionne pour les grilles de taille arbitraire et toutes les entrées. Cela prend très très longtemps de fonctionner sur la plupart des réseaux.

Le remplissage d'inondation est extrêmement inefficace et repose sur la récursivité. Vous devrez peut-être agrandir votre pile si elle est très petite. Le système de force brute utilise une chaîne pour contenir les numéros et un simple ajout avec transfert pour parcourir toutes les options possibles. Ceci est également extrêmement inefficace car il répète la plupart des étapes quadrillions de fois.

Malheureusement, je n'ai pas pu tester tous les tests, car je vais mourir de vieillesse avant la fin.

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

Autant que je sache, c'est le gagnant actuel. Le concours exige que:

Votre programme doit être entièrement déterministe; Les solutions pseudo-aléatoires sont autorisées, mais le programme doit générer la même sortie pour le même scénario de test à chaque fois.

Vérifier

Le programme gagnant prendra le moins de mesures possible pour résoudre les 100 000 tests élémentaires contenus dans ce fichier (fichier texte compressé, 14,23 Mo). Si deux solutions prennent le même nombre d'étapes (par exemple, si les deux ont trouvé la stratégie optimale), le programme le plus court l'emportera.

Comme cela trouve toujours le plus petit nombre d’étapes pour compléter chaque tableau et aucun des autres ne le fait, il est actuellement en avance. Si quelqu'un peut proposer un programme plus court, il pourrait gagner, je présente donc la version optimisée suivante. L'exécution est un peu plus lente, mais le temps d'exécution ne fait pas partie des conditions gagnantes:

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

Jusqu'à présent, c'est la seule entrée qui obtient la solution la plus optimale à chaque fois. Je dirais que c'est aussi une meilleure solution de référence de dernière place. En fait, je ne suis pas convaincu qu'il existe un meilleur moyen de garantir une solution optimale dans tous les cas, et personne d'autre n'a prouvé le contraire.
utilisateur

1
Tant que vous ne pourrez pas trouver le nombre exact d’étapes, je ne peux pas accepter cette solution, même si elle est (théoriquement) la meilleure.
Joe Z.

En outre, la taille de la grille est 19 et non 9.
Joe Z.

D'accord, j'ai corrigé la taille de la grille. Est-ce que quelqu'un sait comment calculer le nombre minimum d'étapes théorique requis?
utilisateur

Nan. Vous devez utiliser un programme pour résoudre ce problème, ce que vous avez actuellement.
Joe Z.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.