Flood Paint AIを作成する


34

Flood Paintのゲームでは、ゲームの目標は、ボード全体をできるだけ少ないターンで同じ色にすることです。

ゲームは次のようなボードから始まります。

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

現在、ボードの中央の数(色を表す)は3です。各ターン、中央の正方形の色が変わり、水平または垂直に移動することで中心から到達できる同じ色のすべての正方形(すなわち、中央の正方形の洪水領域では)色が変化します。したがって、中央の正方形の色が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

中央の3の左側にあった3も色が変わります。これで、中央の1から合計5つの5に到達できるため、色を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

ペイントされた領域のサイズは再び劇的に増加します。

あなたの仕事は、選択したどのような形式でも、入力として1から6までの19行19列の色を受け取るプログラムを作成することです。

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

そして、再び選択した形式で、中央の正方形が各ターンに変わる色のシーケンスを返します。

263142421236425431645152623645465646213545631465

各移動シーケンスの最後に、19行19列のグリッドの正方形はすべて同じ色でなければなりません。

プログラムは完全に決定的でなければなりません。擬似ランダム解は許可されますが、プログラムは毎回同じテストケースに対して同じ出力を生成する必要があります。

受賞プログラムは、このファイル(zip形式のテキストファイル、14.23 MB)で見つかったすべての100,000個のテストケースを解決するために、最小限の手順で済みます。2つのソリューションのステップ数が同じ場合(たとえば、両方が最適な戦略を見つけた場合)、短いプログラムが勝ちます。


BurntPizzaは、テスト結果を検証するためのプログラムをJavaで作成しました。このプログラムを使用するには、サブミットを実行し、出力をというファイルにパイプしますsteps.txt。次に、このプログラムの実行steps.txtfloodtest同じディレクトリ内のファイルを。エントリが有効であり、すべてのファイルに対して正しいソリューションを生成する場合、すべてのテストに合格し、返されるAll 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.");
    }
}

また、スコアボードは、結果が実際にスコアでソートされておらず、ここでは実際に重要なためです:

  1. 1,985,078 -smack42、Java
  2. 2,075,452 -user1502040、C
  3. 2,098,382-ティグロウ、C#
  4. 2,155,834 -CoderTao、C#
  5. 2,201,995 -MrBackend、Java
  6. 2,383,569 -CoderTao、C#
  7. 2,384,020-ヘルヤン、C
  8. 2,403,189 -Origineil、ジャワ
  9. 2,445,761-ヘルヤン、C
  10. 2,475,056-ジェレミーリスト、ハスケル
  11. 2,480,714 -SteelTermite、C(2,395バイト)
  12. 2,480,714 -Java、ヘルジャン(4,702バイト)
  13. 2,588,847 -BurntPizza、Java(2,748バイト)
  14. 2,588,847 -Gero3、node.js(4,641バイト)
  15. 2,979,145 -Teun Pronk、Delphi XE3
  16. 4,780,841 -BurntPizza、Java
  17. 10,800,000 -Joe Z.、Python

2
あなた自身の提出物から判断すると、出力に実際にスペースが含まれていてはいけませんか?
マーティンエンダー14

5
テスト入力データには数字の間にスペースがないことに注意してください。
nderscore 14

3
あなたはまだそれを書くことができます。それが現在の勝者を下回っている場合、受け入れられた答えを変更します。
ジョーZ. 14

4
時間の制約は、「実行して実際の結果をここに投稿するには、十分に高速である必要がある」です。
ジョーZ.

2
@AlexanderRevo私はファイルを移動しなかったと思ったが、どうやらリンクはアップし、私がそうしなくても変更されたようだ。再度リンクを示します。
ジョーZ.

回答:


4

Java-1,985,078ステップ

https://github.com/smack42/ColorFill

別の遅刻。1,985,078ステップを含む結果ファイルは、ここにあります

いくつかの背景情報:

数年前、私がゲームFlood-itのクローンを自分でプログラミングし始めたときに、この課題を発見しました。

「不完全なベスト」DFSおよびA *アルゴリズム
最初から、このゲームに適したソルバーアルゴリズムを作成したかったのです。時間が経つにつれて、さまざまな不完全な検索を行ういくつかの戦略(ここでの他のプログラムで使用されるものと同様)を組み込み、各ソリューションに対してそれらの戦略の最良の結果を使用することでソルバーを改善できました。JavaでtigrouのA *アルゴリズムを再実装し、それをソルバーに追加して、tigrouの結果よりも全体的に優れたソリューションを実現しました。

完全なDFSアルゴリズム次に
、常に最適なソリューションを見つけるアルゴリズムに注目しました。私は徹底的な深さ優先検索戦略を最適化するために多くの努力を費やしました。検索を高速化するために、すべての探索された状態を保存するハッシュマップを含めて、検索で再び探索されないようにしました。このアルゴリズムは問題なく動作し、14x14のすべてのパズルを迅速に解決しますが、このコードチャレンジの19x19のパズルでは、メモリを大量に使用し、実行が非常に遅くなります。

Puchert A *アルゴリズム
数か月前、AaronとSimon PuchertによるFlood-Itソルバーを見るために連絡がありました。このプログラムは、許容可能なヒューリスティック(ティグロウとは対照的)を備えたA *タイプのアルゴリズムを使用し、ジャンプポイント検索と同様のプルーニングを移動します。このプログラムは非常に高速であり、最適なソリューションを見つけることがすぐにわかりました!

もちろん、この素晴らしいアルゴリズムを再実装して、プログラムに追加する必要がありました。Puchert兄弟によるオリジナルのC ++プログラムとほぼ同じ速度で実行されるように、Javaプログラムを最適化する努力をしました。それから、私はこの挑戦の100,000のテストケースで試みをすることに決めました。私のマシンでは、Puchert A *アルゴリズムの実装を使用して、プログラムが120時間以上実行され、1,985,078ステップを見つけました。

これは、最適でないソリューションをもたらすバグがプログラムにない限り、この課題に対する可能な限り最良のソリューションです。フィードバックは大歓迎です!

編集:ここにコードの関連部分を追加しました:

クラス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);
    }

}

クラス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;
    }
}

クラス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
PPCGへようこそ!ソルバーに関連するコードを回答自体に含めて、自己完結型にすることができますか?githubリポジトリが移動したりダウンしたりする必要がありますか?
マーティンエンダー

ここにコードの最も関連する部分を追加しました。「Puchert A *アルゴリズム」の実装です。(このコードの抜粋は自己完結型ではなく、そのままコンパイルすることはできません)
-smack42

誰かがこのための完璧な/最適なソリューションを見つけてうれしいです。しかし、反対側では、これ以上の競争/新しい答えがないことを意味します。
tigrou

15

C#-2,098,382ステップ

私は多くのことを試みますが、それらのほとんどは失敗し、最近までまったく機能しませんでした。私は答えを投稿するのに十分な興味深いものを得ました。

これをさらに改善する方法は確かにあります。2Mのステップを踏むことは可能だと思います。

7 hours結果を生成するのに約かかりました。誰かがそれらをチェックしたい場合に備えて、すべてのソリューションを含むtxtファイルは次のとおりです: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));                                             
                    }       
                }               
            }
        }
    }
}

仕組みの詳細:

A * Pathfindingアルゴリズムを使用します。

難しいのは良いものを見つけることheuristicです。場合heuristic、それはコストを過小評価し、それはほとんどのようにのように実行しますダイクストラアルゴリズムと理由6色と19路盤ボードの複雑さのそれは永遠に実行されます。コストを過大評価すると、すぐにソリューションに収束しますが、良いものはまったく提供されません(26の動きが19であったようなことが可能です)。heuristicソリューションに到達するための正確な残りのステップ数を提供する完璧なものを見つけることが最善です(高速で、可能な限り最高のソリューションを提供します)。それは(私が間違っていない限り)不可能です。実際にボード自体を解決する必要がありますが、これは実際にしようとしていることです(鶏と卵の問題)

私は多くのことを試しましたが、ここでうまくいったのはheuristic

  • 現在のボードのグラフを作成して評価します。それぞれnodeは、連続した同じ色の正方形のセットを表します。それを使用してgraph、中心から他のノードまでの正確な最小距離を簡単に計算できます。たとえば、中心から左上までの距離は10になります。これは、少なくとも10色で区切られているためです。
  • 計算のためにheuristic:私は最後まで現在のボードをプレイします。各ステップで、ルートから他のすべてのノードまでの距離の合計を最小化する色を選択します。
  • その終わりに達するのに必要な動きの数はheuristicです。

  • Estimated cost(A *で使用)= moves so far+heuristic

コストをわずかに過大評価するため、完全ではありません(したがって、最適でないソリューションが見つかります)。とにかく、計算して良い結果を出すのは速いです。

グラフを使用してすべての操作を実行することで、大幅な速度向上を実現できました。最初は2-dimensionアレイがありました。私はそれをあふれさせ、必要に応じてグラフを再計算します(例:ヒューリスティックを計算するため)。これで、すべてが最初にのみ計算されたグラフを使用して行われます。ステップをシミュレートするために、塗りつぶしはもう必要ありません。代わりにノードをマージします。これは非常に高速です。


2
code blocksテキストを強調するために使用しないでください。我々は持っているイタリックボールドそのため。
ファンドモニカの訴訟

10

Python – 10,800,000ステップ

最終的な参照ソリューションとして、次のシーケンスを検討してください。

print "123456" * 18

すべての色のn時間を繰り返すことは、n離れたすべての正方形が中央の正方形と同じ色であることが保証されることを意味します。すべての正方形は中心から最大で18歩離れているため、18サイクルですべての正方形が同じ色であることが保証されます。ほとんどの場合、それ以下で終了しますが、すべての正方形が同じ色になったらすぐにプログラムを停止する必要はありません。そうするほうがはるかに有益です。この一定の手順は、テストケースごとに108ステップ、合計10,800,000です。


総当たり法、真剣に...?ジョー、私はあなたがよりよく知るためにもう少し経験があると思った、仲間?
WallyWest 14

2
それは深刻なエントリとして意図されていません。最後にキャッチオールとして機能するためのソリューションとして具体的に記載していることに注意してください。深刻なエントリは、これよりもはるかに低いスコアを持ちます。
ジョーZ. 14

スペースはありませんか?のような1 2 3 4 5 6 ...現在のソリューションの代わりに123456...
user80551 14

1
コードゴルフの最適なアルゴリズムになります(他の言語では「print」は文字数が多すぎます)。
ランチャー

1
また、18ステップの最悪のケースさえ可能であると思いません。しかし、もちろん、それよりも悪いケースはないことを知っているので、これは間違いなく動作します:)
Cruncher

8

Java-2,480,714ステップ

前に少し間違えました(ループではなくループの前に1つの重要な文を置きました:

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

これを実行するのにどれくらい時間がかかりましたか?
アレクサンダーブレット14

@ ali0sha私のパソコンは30分もかかりませんでした
Herjan 14

まあがらくた。私は30分走っています...
アレクサンダー・ブレット14

ところで、ゴルフは必要ありません。
ジョーZ.

1
@ m.buettner悪魔と言えば、誰かあなたがそれを言ってから3時間後にこの解決策を結び付けました(そしてより短いコードを持っていました)。
ジョーZ.

5

C-2,075,452

私はパーティーに非常に遅れていることを知っていますが、私はこの挑戦を見て、行きたいと思いました。

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

このアルゴリズムは、Thompsonサンプリングを使用したMonte-Carlo Tree Searchと、検索スペースを削減する転置テーブルに基づいています。私のマシンでは約12時間かかります。結果を確認したい場合は、https://dropfile.to/pvjYDMVで見つけることができます


ユーザーsmack42は、プログラムのクラッシュを修正するhash ^= zobrist_table[i][(int)solution[i]];ためhash ^= zobrist_table[i%len][(int)solution[i]];に変更することを提案します。
スティーブン

@StepHen lenよりもスコアが大きくなる方法がわかりません。このクラッシュを引き起こす入力はありますか?smak42との会話へのリンクはありますか?クラッシュすることはできなくても、パフォーマンスに問題のあるにきびではないコードで安全な側にいることに害はないと思います。
user1502040

いいえ、申し訳ありませんが、提案された編集中です。レビューは次のとおり
スティーブン

これで私を破って+1。しかし、注意してください、来るいくつかの改善があるかもしれません;)
tigrou

4

Java- 2,434,108 2,588,847ステップ

4/26の時点で現在勝っている(ヘルヤンより先に46Kまで)

Welp、MrBackendが私を打ち負かしただけでなく、一見良いスコアを出すバグを見つけました。現在は修正されています(検証者でもありました!Ack)が、残念ながら今のところ、王冠を取り戻そうとする時間はありません。後で試行します。

これは私の他のソリューションに基づいていますが、塗りつぶしエッジに最も一般的な色でペイントする代わりに、同じ色の多くの隣接する正方形を持つエッジを露出させる色でペイントします。LookAheadPainterと呼びます。必要に応じて後でゴルフします。

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

編集:私は検証者を書いた、自由に使用して、それはあなたのプログラムが出力するステップとfloodtestファイルを含むsteps.txtファイルを期待しています:編集-編集:(OPを参照)

誰かが問題を見つけたら、私に報告してください!


いいね、ピザ!そして、その検証者は確かにスマートなものです!OPはこのような制御プログラムを作成する必要がありました(多くの問題を解決できたはずです)。
ヘルヤン14

3

C-2,480,714ステップ

まだ最適ではありませんが、より高速になり、スコアが向上しました。

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

ウィレム、説明に私に言及してくれてありがとう。あなたの恵みを光栄に思います。
ヘルヤン14

親愛なるヘルジャン
SteelTermite

ちなみに、「Herjanのスコアよりわずかに高いスコア」というあなたの声明はすでに時代遅れです。
ヘルヤン14

1
515歩先、「=」を追加/削除することを聞いたことがありますが、比較すると、heheh
Herjan

確かに、ヘルジャン。あなたの提案に従って、提出物を更新します。
SteelTermite 14

3

Java- 2,245,529 2,201,995ステップ

深さ5での並列およびキャッシュツリー検索により、「島」の数を最小限に抑えます。深さ4から深さ5への改善は非常に小さかったので、それ以上改善することにはあまり意味がないと思います。しかし、改善が必要な場合は、すべてを再計算するのではなく、島の数を2つの状態の差分として計算するという直感に基づいています。

現在、検証者の入力形式がわかるまで、stdoutに出力します。

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

}

印象的で、ステップをファイルに書き込むことができますか?確認できるように?
ヘルヤン14

@Herjanは、彼のコードが自己検証しているようです。isSolved()
BurntPizza 14

@BurntPizzaだから?私のコードは自己検証もしています(笑)つまり、それは自分のコードと同じくらい間違っている可能性があります。
ヘルヤン14

isSolved()は検証用ではなく、終了用です。書き込みに関しては-次のバージョンで行います。
MrBackend 14

4で見つかったステップの数が24、はるかに効率的なランタイムにつながる場合にのみ、5ステップの深さで検索できるようにするヒューリスティックがあれば興味があります。
ジョーZ. 14

2

私の最後のエントリ:C-2,384,020ステップ

今回は「すべての可能性をチェックする」ものです...このスコアは、深さを3に設定すると得られます。深さ5で〜2.1Mステップが必要です...遅すぎます。深さ20+は、可能な限り最小のステップを提供します(すべての一致と最短の勝利をチェックします)。私は他のCエントリを好みます。これはこの投稿にもあります。

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

Cで記述されたもう1つの改善されたAI-2,445,761ステップ

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

...そして私を打つために〜200K;)
MrBackend 14

各エントリを個別の回答として投稿する必要があります。
ジョーZ.

@JoeZ。申し訳ありませんが、スパムのように感じたため、それらを1つの回答にまとめることにしました(最良(最良=最小のステップ数のAI)のみがカウントされるため、問題ではありません)。少なくともそれは私が考えたことです。
ヘルヤン14

1

Java- 2,610,797 4,780,841ステップ

(Fill-Bugが修正され、スコアは劇的に悪化しました-_-)

これは私の基本的な参照アルゴリズムの提出であり、単にペイントされた領域の端にある正方形のヒストグラムを作成し、最も一般的な色でペイントします。数分で100kを実行します。

明らかに勝つことはありませんが、確かに最後ではありません。私はおそらく賢いもののために別の提出をするでしょう。このアルゴリズムを出発点として自由に使用してください。

全出力のコメント行のコメントを外します。デフォルトでは、実行されたステップ数を印刷します。

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

860文字までのゴルフ(書式設定の改行は含まれません)ですが、試してみたいと感じた場合はさらに縮小できます。

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

「確かに最後ではない」という唯一の理由は、私の参照ソリューションが物事を埋めるためにあるからです。これは、現時点では他の人にすべて提出のうち、実際には最後の場所です:P
ジョー・Z.

@JoeZ。まあ、それはSteelTermiteの前にありましたが、彼は彼を改善しました。私はこれを「素朴からの次の論理的ステップ」アプローチとして意味しました。それがうまくいくかどうか心配です; P
BurntPizza 14

1

Haskell-2,475,056ステップ

アルゴリズムは、コメントでMrBackendによって提案されたものに似ています。違いは次のとおりです。彼の提案は、最もコストの高い正方形への最も安価なパスを見つけ、私はすべてのステップでグラフの偏心を貪欲に減らします。

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

まだ実行は終了していますか?
ジョーZ. 14

まだ、夜通し走らせたらもう終わったかもしれないが、ファンはうるさいので、コンピューターを冬眠した。今再び実行され、仕事から家に帰ると再びチェックします。
ジェレミーリスト14

スタックオーバーフローが原因でクラッシュしましたが、それを回避するために変更しました。
ジェレミーリスト14

1

C#-2,383,569

これは、Herjanが同じ番号を投稿した後、候補ソリューションの生成順序を巧妙に逆にしたことを除いて、最適な改善のパス(HerjanのCエントリと同様/同じ)を大まかに選択する可能性のあるソリューションの深層トラバーサルです。ただし、実行には12時間以上かかります。

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)

これはブルートフォースでの私の試みでした。しかし!単一の深さの「最良の」選択の私の最初の実装は以下をもたらしました。

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

両方に使用されるコードは、他の可能な動きの「スナップショット」を保存し、それらすべてに対してアルゴリズムを実行するブルートフォースと同じです。


  • 課題

「マルチ」パスアプローチで実行すると、ランダムな障害が発生します。ユニットテストで最初の100個のパズルエントリを設定すると、100%のパスを達成できますが、100%の時間は達成できません。これを補うために、失敗時に現在のパズル番号を追跡し、最後のスレッドが中断したところから新しいスレッドを開始しました。各スレッドは、それぞれの結果をファイルに書き込みました。その後、ファイルプールは1つのファイルに圧縮されました。

  • アプローチ

Nodeボードのタイル/正方形を表し、そのすべての近隣への参照を格納します。3つのトラックSet<Node>の変数を:RemainingPaintedTargets。各反復でTargetsすべてをグループ化するcandidate値によってノード、選択しますtarget value、「影響を受ける」ノードの数で。これらの影響を受けるノードは、次の反復のターゲットになります。

ソースは多くのクラスに広がっており、スニペットは全体のコンテキストからあまり意味がありません。私のソースはGitHubで閲覧できます。私も台無しに、視覚化のためのJSFiddleデモをいじりました。

それにもかかわらず、私の主力メソッドは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

これは、他のソルバーと同じ「最良の子孫を探す」アプローチと事実上同じですが、わずかな最適化を行うだけで、並列処理により、10時間弱で深さ5に到達できます。これをテストする過程で、元のバグも発見しました。これにより、アルゴリズムが最終状態への非効率的なルートをたどる場合があります(スコア= 64の状態の深さを考慮していませんでした。 = 7)。

これと以前のソルバーの主な違いは、フラッドゲームの状態をメモリに保持するため、6 ^ 5の状態を再生成する必要がないことです。実行中のCPU使用率に基づいて、これはCPUバウンドからメモリ帯​​域幅バウンドに移行したと確信しています。とても楽しい。たくさんの罪。

編集:理由により、深さ5のアルゴリズムが常に最良の結果を生成するとは限りません。パフォーマンスを向上させるには、深さ5 ...と4 ...と3と2と1を実行して、どれが最適かを見てみましょう。さらに40kの動きを削りました。深さ5が大部分の時間であるため、4から1を追加しても、実行時間が10時間から11時間に増加するだけです。わーい!

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

私はあなたのコードを試しましたが、コンパイルしません。1つの解決メソッド呼び出しの近くにエラーがあります。それに加えて、いくつかの「使用中」のステートメントもありません。とにかく、プログラムが2.1Mステップですべてを解決するだけなら、おめでとう、これはかなり印象的です。
ティグー

@tigrouステートメントの使用に問題はありません。コールの呼び出しエラーを修正しました。これは、コードを再(コピー/貼り付け)するのではなく、コードを更新しようとすることによるアーティファクトでした。ごめんなさい。
CoderTao

ブラーグ。==名前空間のインポートを使用するつもりでした。それも修正します。
CoderTao

11時間で深さ5のすべてのボードを解決するには、どのCPUを使用しますか?I5 760@2.8Ghzでプログラムを実行しました。256ボードの各チャンクの出力には30分かかりました。それに基づいて、100.000ボードを解決するのに8日かかりました。その間、CPUは80〜100%の使用率で跳ね返り、4つのコアすべてが使用されていました。テストの実行に使用されたvirtualboxマシンに問題があるかもしれませんが、それはあなたよりも約16倍遅いです(あなたはそれが11時間かかったと言いました)。
ティグー

@tigrou i5 750@2.67(3〜4年前のハードウェア)で実行しています。VSでは、デバッグモードとリリースモードの違いは50%ですが、16倍の違いを説明できるとは思いません。Linuxホストの下で実行している場合は、モノラルでコンパイルしてみてください
CoderTao

1

Delphi XE3 2,979,145ステップ

わかりましたので、これは私の試みです。私は変化する部分をブロブと呼びます。ターンごとに配列のコピーを作成し、可能なすべての色をテストして、どの色が最大のブロブを生成するかを確認します。

すべてのパズルを3時間6分で実行します

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.

ブルートフォースバックトレース方法についても考えます。
この週末は楽しいかもしれません^^


0

Javascript / node.js-2,588,847

アルゴリズムは、事前に計算された領域と計算間の差分状態を利用するため、ここでのアルゴリズムとは少し異なります。JavaScriptのせいで速度が心配な場合は、ここで10分未満で実行されます。

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コード。任意のサイズのグリッドとすべての入力に対して機能します。ほとんどのグリッドで実行するには、非常に長い時間がかかります。

フラッドフィルは非常に非効率的で、再帰に依存しています。スタックが非常に小さい場合は、スタックを大きくする必要がある場合があります。ブルートフォースシステムは、文字列を使用して数値を保持し、単純なキャリー付き加算を使用して、すべての可能なオプションを循環します。これは、ほとんどのステップを数千回繰り返すため、非常に非効率的です。

残念ながら、すべてのテストケースに対してテストすることはできませんでした。終了する前に老齢で死ぬからです。

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

私の知る限り、これが現在の勝者です。競争には以下が必要です。

プログラムは完全に決定的でなければなりません。擬似ランダム解は許可されますが、プログラムは毎回同じテストケースに対して同じ出力を生成する必要があります。

チェック

受賞プログラムは、このファイル(zip形式のテキストファイル、14.23 MB)で見つかったすべての100,000個のテストケースを解決するために、最小限の手順で済みます。2つのソリューションのステップ数が同じ場合(たとえば、両方が最適な戦略を見つけた場合)、短いプログラムが勝ちます。

これは、常にすべてのボードを完了するための最小数のステップを見つけるため、他のボードはどれも実行しないため、現在は先を行っています。誰かが短いプログラムを思い付くことができれば、彼らは勝つことができるので、次のサイズ最適化バージョンを提示します。実行は少し遅くなりますが、実行時間は勝利条件の一部ではありません。

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

これまでのところ、常に最適なソリューションを取得できる唯一のエントリです。私はそれがより良い最後の場所の参照ソリューションでもあると主張します。実際、私は実際にあらゆる場合に最適なソリューションを得ることが保証されるより良い方法があるとは確信していません。
ユーザー14

1
あなたが実際にそれが取るステップの正確な数を見つけることができるまでは、私はそれがあっても、このソリューションを受け入れることができないです(理論上は)最良のものを。
ジョーZ. 14

また、グリッドサイズは19、ではない9です
ジョー・Z.

さて、グリッドサイズを修正しました。必要なステップの理論上の最小数を計算する方法を知っている人はいますか?
ユーザー14

いや。あなたはそれを解決するためにプログラムを使用しなければなりません。それはあなたが今持っているものです。
ジョーZ.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.