web-dev-qa-db-ja.com

お絵かきロジックの解決(ピクロス)

金曜日の午後です。楽しいパズル/アルゴリズムの問​​題を解決しましょう。

私のお気に入りのニンテンドーの1つDSゲームは ピクロスDS です。ゲームは非常にシンプルで、 お絵かきロジック と呼ばれるパズルを解く必要があります。試すことができますここに簡単なオンラインピクロスクローン: TylerKのピクロス

お絵かきロジックはグリッドであり、グリッドのすべての行と列に一連の番号が定義されています。数字は、その行/列の「塗りつぶされた」正方形のブロックを定義しますが、ブロックの両側の塗りつぶされていない領域は定義されていません。たとえば、次のような行がある場合:


(出典: Steam-punk.net

その行の可能な解決策は次のとおりです。


(出典: Steam-punk.net

(出典: Steam-punk.net

等.

「45」は、行のどこかに、4つの連続したブロックが入力され、続いて5つの連続したブロックが入力されていることを示しています。これらが入力された唯一のブロックであり、前後のスペースの量は次のとおりです。定義されていません。

すべての行と列がそれらの定義を満たせば、矛盾することなくパズルが完成します。

コンセプトは非常にシンプルなゲームですが、それらのいくつかを手動で解決するにはかなりの時間がかかる場合があります。ピクロスDSのパズルは、25x20グリッドまで徐々にサイズが大きくなります。これは、通常、解決するのに約30分かかります。

しかし、解決するプログラムを書くのはとても簡単なゲームだといつも思います。私はそれに慣れたことはありませんが、おそらくここの何人かの人々は挑戦を楽しむでしょう。かなりの数の解決策が投稿された場​​合、私はそれらを互いに大きなパズルでベンチマークします。これは、 Paolo BergantinoがBoggleの質問でここで行ったもの と同様です。 Nonogram Wikipediaページ パズルを攻撃する方法については、参照したい場合はかなりの情報があります。

これがTylerKのPicrossのパズル#1の簡単に解析できる定義なので、プログラムにフィードするものがあります。 1行目はパズルの次元(おそらく不要)、2行目は行の定義、3行目は列の定義です。これは最初に頭に浮かんだことなので、誰かがより良い入力形式を考えられる場合は、この投稿にコメントするか編集して、それを含めてください。

15 15
15,4 5,2 4,1 3,2,2,2 4 3,2 6 2,2 1 6 2,2 1 1 4 2,1 1,1 3 2 1,2 2 1 2 1,3 3 2 1,9
4 4,3 1 2 3,2 1 2 2,2 1 1,1 4 2,1 3,1 8,1 3 1 1,1 4 2 1,1 4,2 4 3,3 3 3,4 1,10 3,10
25
Chad Birch

はい、問題はNP完全ですが、それは、パズルのサイズが大きくなるにつれて、(ほとんど)指数関数的に増大する実行時間で立ち往生していることを意味するだけです。しかし、実際のパズルのサイズは大きくなりません。 100x100より大きいパズルを作成することを気にする人はほとんどおらず、大多数は50x50より小さいパズルです。本や雑誌に掲載されているパズルの95%をほんの一瞬で解くソルバーを構築することは、実際には特に難しいことではありません。かなり単純な深さ優先探索システムがそれを行います。

それほど明白ではないのは、非常に厄介で、ほとんどの単純なソルバーの実行時間が爆発するパズルのごく一部があるということです。これらのほとんどは、人間が解くのが困難または不可能な、設計が不適切なパズルですが、経験豊富な人間のソルバーが、ほとんどのAIプログラムが管理できるよりも深い洞察を使用して、簡単に解くことができる特に巧妙なパズルです。

私はほとんどのパズルを非常に迅速に解決する独自のソルバーを作成し、 他の多くのソルバーの調査 パフォーマンスを比較するベンチマーク結果を作成しました。また、賢い人間にとって難しくないパズルがほとんどのプログラムで非常に難しいことを示す、興味深いクラスのハードパズル(ドミノパズル)についても説明します。私のソルバーとドミノパズルへのリンクは調査にあります。

まだまだ改善の余地があり、新鮮なアイデアを持った人にぜひ試してもらいたいと思います。しかし、明らかなことが行われ、それらを再度行うことにはあまり意味がないことは注目に値します。

21
Jan Wolter

ノノグラムソリューションが存在するかどうか/一意であるかどうかを判断するのはNP困難です。 http://en.wikipedia.org/wiki/Nonogram#cite_note- を参照してください。

6
Dave

「最初の」行を配置しようとするのではなく、複数の強制値を持つ可能性のある「最も制約のある」行または列から情報を取得しようとすると、検索が大幅に削減されます。簡単な指示は、行/列のすべての値を合計し、#_ of_values -1を追加してから、そのような値が最大の行/列を探します(または、この値と行または列の数の差が最小の場合)。行と列が異なります)。したがって、25x25のパズルがあり、行の1つが「5 1 1 6 1 1 3」の場合、その行の値は24です。これは、非常に制約されていることを意味します。つまり、空白の正方形の1つを除くすべての相対位置です。その行のは既知であり、最後の空白の正方形は8つの異なる相対位置のいずれかになります。したがって、塗りつぶされた正方形の各グループには、2つの可能性しかありません。余分な空白の正方形がこのグループの左側にあるか、余分な空白の正方形がこのグループの右側にあります。したがって、たとえば、6つのグループの塗りつぶされた正方形のうち5つはすでにわかっています。

ある方向からの強制値を取得すると、既知の情報と交差する他の方向からのグループをさらに制約します。したがって、より多くの情報が知られるようになると、列と行の間を行ったり来たりするのが最善のアプローチになる可能性があります。これは、手作業で解決する必要がある方法とほぼ同じです。

4
dewtell

本当の問題は、誰かが人間よりも速くパズルを解くアルゴリズムを思い付くことができるかどうかです。これは、参照パズルなどの比較的簡単なパズルでは簡単ですが、パズルが大きくなると、ここのアルゴリズムのほとんどがすぐに遅くなります。これが私が解こうとしたパズルです。問題は、たとえば4行目に2220075の可能な組み合わせがあることです。チャーリーのアルゴリズムが一時的に行3に対して間違った行を受け入れる場合、行4に対してこれらすべての組み合わせを実行します。これは、アルゴリズムが行2で行った間違いについて行35で矛盾する場合は言うまでもありません。

私のアルゴリズムはジョンのアルゴリズムと似ていました。 64ビットデスクトップでx86モードで実行できませんでした。 64ビットモードに切り替えて一晩実行すると、午前中はコンピューターが完全に使用できなくなりました。このプロセスでは8ギガのメモリ(デスクトップでは物理的に8ギガ)が予約されていましたが、必死のスワッピングが原因でコンピュータが応答しませんでした。そして、それはすべての可能な行を解決していませんでした。言うまでもなく、可能な列の組み合わせにも触れていませんでした。

List<List<int>> rows =
                new List<List<int>>()
                {
                    new List<int> { 8,29,4 },
                    new List<int> { 6,4,25,4,3 },
                    new List<int> { 5,3,2,3,9,4,2,1,3 },
                    new List<int> { 4,2,2,2,2,1,2,2 },
                    new List<int> { 4,1,1,9,10,2,2,1 },
                    new List<int> { 3,2,6,5,5,1,1 },
                    new List<int> { 3,1,5,5,1,1 },
                    new List<int> { 3,1,4,4,1,1 },
                    new List<int> { 3,1,4,4,1,1 },
                    new List<int> { 3,1,3,3,1,1 },
                    new List<int> { 3,1,3,6,2 },
                    new List<int> { 3,1,2,3,2,4,2 },
                    new List<int> { 4,3,1,8,7,1,2,3 },
                    new List<int> { 4,2,1,12,11,1,2,4 },
                    new List<int> { 5,1,2,7,2,2,6,1,1,4 },
                    new List<int> { 4,1,1,1,6,2,2,6,1,2,1,3 },
                    new List<int> { 4,1,1,2,4,3,4,3,1,1,1,1,3 },
                    new List<int> { 4,1,1,2,1,4,1,2,3,2,1,2,2 },
                    new List<int> { 3,1,1,1,2,5,6,1,1,1,3,2 },
                    new List<int> { 3,2,1,1,2,1,5,4,4,2,1,2,1,2 },
                    new List<int> { 3,2,2,1,1,4,2,2,3,1,1,2,1,1,2 },
                    new List<int> { 3,1,3,2,1,1,4,1,5,3,2,1,3,1,2 },
                    new List<int> { 3,1,2,1,2,1,3,7,4,1,4,2,2 },
                    new List<int> { 2,1,4,1,1,1,2,6,2,2,2,3,2,1 },
                    new List<int> { 2,2,4,1,2,1,2,5,2,1,1,3,2,1 },
                    new List<int> { 2,2,1,4,1,1,3,3,2,1,4,4,1 },
                    new List<int> { 2,3,3,2,1,3,3,7,4,1 },
                    new List<int> { 2,3,2,4,5,8,1,2,1 },
                    new List<int> { 1,1,3,11,6,7,1,3,1 },
                    new List<int> { 1,1,2,2,13,10,2,3,2 },
                    new List<int> { 1,2,3,1,6,1,1,7,1,5,2 },
                    new List<int> { 1,1,3,2,6,1,1,1,1,4,1,4,2 },
                    new List<int> { 1,1,6,7,2,4,2,5,6,1 },
                    new List<int> { 1,1,2,3,1,4,2,2,11,2,1 },
                    new List<int> { 1,1,1,1,2,1,5,10,1,1,1 },
                    new List<int> { 1,1,1,1,4,7,4,10,1,1,1 },
                    new List<int> { 1,2,1,1,28,1,1,3 },
                    new List<int> { 1,2,1,2,27,2,1,3 },
                    new List<int> { 1,1,1,1,26,1,1,1,1 },
                    new List<int> { 2,3,1,28,2,1,2,1 }
                };
            List<List<int>> cols =
                new List<List<int>>()
                {
                    new List<int> { 40 },
                    new List<int> { 28,1 },
                    new List<int> { 23,8 },
                    new List<int> { 5,6,7,4 },
                    new List<int> { 3,6,1,9,3,1 },
                    new List<int> { 2,3,2,5,4,2,2 },
                    new List<int> { 1,2,4,1,2,5,2 },
                    new List<int> { 1,1,4,9,2,3,2 },
                    new List<int> { 2,4,2,6,1,4,3 },
                    new List<int> { 1,4,1,3,4,1,6 },
                    new List<int> { 1,4,3,2,3,5,5 },
                    new List<int> { 2,4,1,2,3,4,1,3 },
                    new List<int> { 1,2,3,4,2,2,4,4,1 },
                    new List<int> { 1,1,2,3,2,1,4,2,4 },
                    new List<int> { 2,3,5,3,3,5,4 },
                    new List<int> { 3,1,6,1,2,5,5 },
                    new List<int> { 3,2,6,2,15 },
                    new List<int> { 3,1,8,2,13 },
                    new List<int> { 2,2,4,5,15 },
                    new List<int> { 2,2,2,2,22 },
                    new List<int> { 2,1,1,1,12,6 },
                    new List<int> { 2,1,10,4,5 },
                    new List<int> { 3,1,3,1,2,4 },
                    new List<int> { 3,1,1,4,3,1,4 },
                    new List<int> { 3,2,2,3,2,2,5 },
                    new List<int> { 3,1,1,5,1,1,5 },
                    new List<int> { 3,1,1,5,1,1,5 },
                    new List<int> { 3,1,1,5,1,1,5 },
                    new List<int> { 3,2,5,2,1,1,4 },
                    new List<int> { 3,1,1,3,2,2,4 },
                    new List<int> { 3,1,6,4,5 },
                    new List<int> { 2,2,12,2,6 },
                    new List<int> { 2,2,1,1,22 },
                    new List<int> { 2,1,2,2,5,15 },
                    new List<int> { 3,1,4,3,2,14 },
                    new List<int> { 3,1,7,2,1,13 },
                    new List<int> { 3,2,6,1,1,6,8 },
                    new List<int> { 3,2,5,2,2,4,7 },
                    new List<int> { 2,1,2,4,1,1,1,4,1,4,2 },
                    new List<int> { 1,1,4,4,3,1,4,5,1 },
                    new List<int> { 1,1,5,1,1,2,1,2,2,3,2 },
                    new List<int> { 1,5,2,2,1,5,5,3 },
                    new List<int> { 1,6,2,1,4,2,6,1 },
                    new List<int> { 1,6,2,6,5,2 },
                    new List<int> { 1,5,3,1,9,2 },
                    new List<int> { 2,2,4,2,6,3 },
                    new List<int> { 1,2,2,2,9,2,1 },
                    new List<int> { 3,5,5,8,4 },
                    new List<int> { 4,13,9 },
                    new List<int> { 27,2 }
                };

著作権は情報技術のタンペレギルド/カイサパイ/フィンランドの醸造所にあります。

2
Mikko Rantanen

これは、「n queens」の問題と同様に、バックトラックを使用した深さ優先探索の非常に明白なケースのようです。キュートな部分は、行/列を配置するだけでなく、ブロックをシフトできることです。

さて、ここに概要があります。

  1. 空のボードから始めて、最初の行を配置します

  2. 次に、そのボードで2番目の行を配置し、列の制約と照合します。合格した場合は、その状態に対して次の行を再帰的に試行します。合格しない場合は、その行の次の可能な配置を試してください。

  3. 制約を満たす行の可能な配置が不足した場合はいつでも、パズルに解決策はありません。そうでなければ、あなたがナナカマドを使い果たしたとき、あなたは問題を解決しました。

これらの行を2進数として扱うことができると便利なので、可能性には自然な順序があります。

2
Charlie Martin

数か月前、C++でお絵かきロジックのソルバーを作成しました。影付きのセルと空白のセルのすべての許容位置を探すだけです。そして、すべてのソリューションステップで、すべての位置に問題がないかどうかを確認します。チャドバーチのお絵かきロジックのタイミングの結果は次のとおりです: http://i.stack.imgur.com/aW95s.png

そして、ミッコ・ランタネンのノングラムのソルバーも試しました: http://i.stack.imgur.com/o1J6I.png

1
klimenkov

今のところ、解決策を具体化するのに十分な時間がありませんが、これが私がそれに取り組む方法です。

「function1」は、上部または側面の数値の制約、およびすでに入力されている正方形と空になっている正方形を考慮して、行または列の可能な組み合わせを決定します。

「function2」は、function1からの出力を論理的に取得し、すべての結果をまとめます。1つのスポットを埋めることができます。

「function3」は、function1からの出力を取得し、論理的またはすべての結果をまとめて取得します。ゼロのあるスポットは空にすることができます。

ボックスが空になるか、入力されなくなるまで、function2とfunction3をすべての行と列に適用し続けます。パズルが解決されたら、完了です。

パズルが解けない場合は、可能性が最も少ない行または列を取り、最初の可能性を適用します。次に、function2とfunction3を新しいボードに適用します。矛盾(行または列の可能性が0)につながる場合は、可能性の適用を解除して、別の可能性を試してください。解決策が見つかるまで、このように繰り返します。

1
John

これが私が見つけたものです:

すべてNP完全な問題は同じ感覚です。残りのケースの次の80%を解決するのはいつでも簡単です。たとえば、ナノグラムは1行に分解されます。ルーチンsolve_one_line(old_state, line_definition, new_state)を記述して、1行について既知の情報を更新し、行と列を繰り返し処理します。その後、いくつかのケースで失敗するため、それらのケースの80%を解決するための何かを記述します。乱数を追加して、これまでに見つけたすべてのケースを解決しますが、最適に悪いものを作成することは可能です。

このパターンに従う他のゲームは、MineSweeper数独です。

並行して考えるのは難しい。たとえば、solve_one_line上記のルーチンは、行で実行されている場合は別の行に影響を与えず、列で実行されている場合は別の列に影響を与えません。

今、あなたは得る:

  all_until_completion(rows):
      solve_one_line(...)
  all_until_completion(cols):
      solve_one_line(...)

これにより、データロックなどを行わずに最大20コア(20x20)を実行できます。次に、各セルがプロセッサであるグラフィックカードで実行することを考えます。次に、どれだけの時間が経過したかに気づきます。

ある時、私はO(n)表記がO()に置き換えられた現代のコンピュータサイエンスの教科書を見ていました。 n、p)表記。これは、単一のプロセッサのアルゴリズムを評価する人がいないためです。 8クイーンソリューションは、高速障害、効率的なメモリ使用を備えた優れた高速再帰アルゴリズムであり、1つのプロセッサでのみ実行されます。

問題はプレイする良い言い訳です。同じことをもっとすりつぶすのは退屈です。より多くの経験が必要になる可能性のあるテクノロジーのリストを調べてください。依存性注入;純粋な関数型プログラミング;ニューラルネット;遺伝的アルゴリズム;速く、ずさんで、制御不能。 GPGPU; OCR;例主導の学習;クラウドソーシング;など。1つを選択し、問題を解決するために何らかの方法でそれを使用してみてください。時々、目標は問題を解決することではなく、未知の領域をさまようことです。

何かを投稿してください。*これは、記事として簡単にすることができます。他の人が遊ぶことができるように、数百ナノグラムのリポジトリがより良いかもしれません。 [リポジトリが存在するかどうか教えてください。存在しない場合は作成します]。何かいいものが見つかったらすぐに投稿を開始します。クリンゴン語に注意してください:おそらく今日is死ぬのに良い日です。私たちはそれを出荷すると言います。

したがって、NPの問題に対する奇妙な並列ソリューションを作成し、それらを共有してください。そして、素晴らしい1日をお過ごしください。

0
Charles Merriam

Steven Simpson は、JavaScriptスクリプトを含む、さまざまなバージョンで無料で利用できるお絵かきロジックソルバーを作成しました。彼はそのウェブサイトでアルゴリズムの詳細について説明しています(たとえば ここ -基本的に、彼は速度と完全性をトレードオフする一連のラインソルバーを使用しており、すべてのラインソルバーが彼はまた、私たちがここに持っているよりも多くの分野をカバーする他のアプローチへのリンクを持っています。

0
dewtell

古典的なお絵かきロジックのパズルの2つの興味深いひねりを指摘させてください。

  • パズルが占有セルの長さをリストするだけではない場合。この公開チャレンジは、一部のセルを占有されているものとして事前に制約しました: http://www.gchq.gov.uk/press_and_media/news_and_features/Pages/Directors-Christmas-puzzle-2015.aspx

  • お絵かきロジックに空の/占有されたセル以上のものが含まれているが、異なる色のパッチを使用してセルを占有している場合。たとえば、 http://onlinenonograms.com ;を参照してください。手作業で解くと、通常のお絵かきロジックよりも解きやすいと感じます。

アルゴリズム設計者にとっての特別な課題は、色付きのお絵かきロジックが水平/垂直の制約を一緒に検討することで大きなメリットを得られることです。通常の行ごとのソルバーは、ここでは明らかに不利です。

0
user1564286