機能、非競争的
更新!大幅なパフォーマンス改善!n = 7は10分以内に完了します!下部の説明をご覧ください!
これは書くのが楽しかったです。これはFuncitonで書かれたこの問題に対するブルートフォースソルバーです。いくつかのファクトイド:
- STDINの整数を受け入れます。整数の後の改行を含む、余分な空白はすべて改行します。
- 0からn − 1(1からnではない)の数字を使用します。
- グリッドを「後方」に埋めるので、一番上の行がを読み取るの
3 2 1 0
ではなく、一番下の行が読み取るソリューションを取得します0 1 2 3
。
- n = 1の場合
0
、正しく出力されます(唯一のソリューション)。
- n = 2およびn = 3の場合の空の出力。
- exeにコンパイルすると、n = 7の場合に約8¼分かかります(パフォーマンスが改善されるまでに約1時間かかりました)。(インタプリタを使用して)コンパイルしなければ、約1.5倍の時間がかかるため、コンパイラを使用する価値があります。
- 個人的なマイルストーンとして、最初に擬似コード言語でプログラムを作成せずにFuncitonプログラム全体を作成したのはこれが初めてです。私は最初に実際のC#でそれを書きました。
- (ただし、これはFuncitonで何かのパフォーマンスを大幅に改善するために変更を行ったのは初めてではありません。それを行ったのは階乗関数でした。乗算のオペランドの順序を入れ替えることにより、乗算アルゴリズムのしくみ。好奇心が強い場合に備えて。)
難しい話は抜きにして:
┌────────────────────────────────────┐ ┌─────────────────┐
│ ┌─┴─╖ ╓───╖ ┌─┴─╖ ┌──────┐ │
│ ┌─────────────┤ · ╟─╢ Ӂ ╟─┤ · ╟───┤ │ │
│ │ ╘═╤═╝ ╙─┬─╜ ╘═╤═╝ ┌─┴─╖ │ │
│ │ └─────┴─────┘ │ ♯ ║ │ │
│ ┌─┴─╖ ╘═╤═╝ │ │
│ ┌────────────┤ · ╟───────────────────────────────┴───┐ │ │
┌─┴─╖ ┌─┴─╖ ┌────╖ ╘═╤═╝ ┌──────────┐ ┌────────┐ ┌─┴─╖│ │
│ ♭ ║ │ × ╟───┤ >> ╟───┴───┘ ┌─┴─╖ │ ┌────╖ └─┤ · ╟┴┐ │
╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌─────┤ · ╟───────┴─┤ << ╟─┐ ╘═╤═╝ │ │
┌───────┴─────┘ ┌────╖ │ │ ╘═╤═╝ ╘══╤═╝ │ │ │ │
│ ┌─────────┤ >> ╟─┘ │ └───────┐ │ │ │ │ │
│ │ ╘══╤═╝ ┌─┴─╖ ╔═══╗ ┌─┴─╖ ┌┐ │ │ ┌─┴─╖ │ │
│ │ ┌┴┐ ┌───────┤ ♫ ║ ┌─╢ 0 ║ ┌─┤ · ╟─┤├─┤ ├─┤ Ӝ ║ │ │
│ │ ╔═══╗ └┬┘ │ ╘═╤═╝ │ ╚═╤═╝ │ ╘═╤═╝ └┘ │ │ ╘═╤═╝ │ │
│ │ ║ 1 ╟───┬┘ ┌─┴─╖ └───┘ ┌─┴─╖ │ │ │ │ │ ┌─┴─╖ │
│ │ ╚═══╝ ┌─┴─╖ │ ɓ ╟─────────────┤ ? ╟─┘ │ ┌─┴─╖ │ ├─┤ · ╟─┴─┐
│ ├─────────┤ · ╟─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴────┤ + ╟─┘ │ ╘═╤═╝ │
┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═╧═╕ ╔═══╗ ┌───╖ ┌─┴─╖ ┌─┴─╖ ╘═══╝ │ │ │
┌─┤ · ╟─┤ · ╟───┐ └┐ └─╢ ├─╢ 0 ╟─┤ ⌑ ╟─┤ ? ╟─┤ · ╟──────────────┘ │ │
│ ╘═╤═╝ ╘═╤═╝ └───┐ ┌┴┐ ╚═╤═╛ ╚═╤═╝ ╘═══╝ ╘═╤═╝ ╘═╤═╝ │ │
│ │ ┌─┴─╖ ┌───╖ │ └┬┘ ┌─┴─╖ ┌─┘ │ │ │ │
│ ┌─┴───┤ · ╟─┤ Җ ╟─┘ └────┤ ? ╟─┴─┐ ┌─────────────┘ │ │
│ │ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │ │╔════╗╔════╗ │ │
│ │ │ ┌──┴─╖ ┌┐ ┌┐ ┌─┴─╖ ┌─┴─╖ │║ 10 ║║ 32 ║ ┌─────────────────┘ │
│ │ │ │ << ╟─┤├─┬─┤├─┤ · ╟─┤ · ╟─┘╚══╤═╝╚╤═══╝ ┌──┴──┐ │
│ │ │ ╘══╤═╝ └┘ │ └┘ ╘═╤═╝ ╘═╤═╝ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ │
│ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ╔═╧═╕ └─┤ ? ╟─┤ · ╟─┤ % ║ │
│ └─────┤ · ╟─┤ · ╟──┤ Ӂ ╟──┤ ɱ ╟─╢ ├───┐ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │
│ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ ╘═══╝ ╚═╤═╛ ┌─┴─╖ ┌─┴─╖ │ └────────────────────┘
│ └─────┤ │ └───┤ ‼ ╟─┤ ‼ ║ │ ┌──────┐
│ │ │ ╘═══╝ ╘═╤═╝ │ │ ┌────┴────╖
│ │ │ ┌─┴─╖ │ │ │ str→int ║
│ │ └──────────────────────┤ · ╟───┴─┐ │ ╘════╤════╝
│ │ ┌─────────╖ ╘═╤═╝ │ ╔═╧═╗ ┌──┴──┐
│ └──────────┤ int→str ╟──────────┘ │ ║ ║ │ ┌───┴───┐
│ ╘═════════╝ │ ╚═══╝ │ │ ┌───╖ │
└───────────────────────────────────────────────────────┘ │ └─┤ × ╟─┘
┌──────────────┐ ╔═══╗ │ ╘═╤═╝
╔════╗ │ ╓───╖ ┌───╖ │ ┌───╢ 0 ║ │ ┌─┴─╖ ╔═══╗
║ −1 ║ └─╢ Ӝ ╟─┤ × ╟──┴──────┐ │ ╚═╤═╝ └───┤ Ӂ ╟─╢ 0 ║
╚═╤══╝ ╙───╜ ╘═╤═╝ │ │ ┌─┴─╖ ╘═╤═╝ ╚═══╝
┌─┴──╖ ┌┐ ┌───╖ ┌┐ ┌─┴──╖ ╔════╗ │ │ ┌─┤ ╟───────┴───────┐
│ << ╟─┤├─┤ ÷ ╟─┤├─┤ << ║ ║ −1 ║ │ │ │ └─┬─╜ ┌─┐ ┌─────┐ │
╘═╤══╝ └┘ ╘═╤═╝ └┘ ╘═╤══╝ ╚═╤══╝ │ │ │ └───┴─┘ │ ┌─┴─╖ │
│ └─┘ └──────┘ │ │ └───────────┘ ┌─┤ ? ╟─┘
└──────────────────────────────┘ ╓───╖ └───────────────┘ ╘═╤═╝
┌───────────╢ Җ ╟────────────┐ │
┌────────────────────────┴───┐ ╙───╜ │
│ ┌─┴────────────────────┐ ┌─┴─╖
┌─┴─╖ ┌─┴─╖ ┌─┴─┤ · ╟──────────────────┐
│ ♯ ║ ┌────────────────────┤ · ╟───────┐ │ ╘═╤═╝ │
╘═╤═╝ │ ╘═╤═╝ │ │ │ ┌───╖ │
┌─────┴───┘ ┌─────────────────┴─┐ ┌───┴───┐ ┌─┴─╖ ┌─┴─╖ ┌─┤ × ╟─┴─┐
│ │ ┌─┴─╖ │ ┌───┴────┤ · ╟─┤ · ╟──────────┤ ╘═╤═╝ │
│ │ ┌───╖ ┌───╖ ┌──┤ · ╟─┘ ┌─┴─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴─╖ │ │
│ ┌────┴─┤ ♭ ╟─┤ × ╟──┘ ╘═╤═╝ │ ┌─┴─╖ ┌───╖└┐ ┌──┴─╖ ┌─┤ · ╟─┘ │
│ │ ╘═══╝ ╘═╤═╝ ┌───╖ │ │ │ × ╟─┤ Ӝ ╟─┴─┤ ÷% ╟─┐ │ ╘═╤═╝ ┌───╖ │
│ ┌─────┴───┐ ┌────┴───┤ Ӝ ╟─┴─┐ │ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ │ │ └───┤ Ӝ ╟─┘
│ ┌─┴─╖ ┌───╖ │ │ ┌────╖ ╘═╤═╝ │ └───┘ ┌─┴─╖ │ │ └────┐ ╘═╤═╝
│ │ × ╟─┤ Ӝ ╟─┘ └─┤ << ╟───┘ ┌─┴─╖ ┌───────┤ · ╟───┐ │ ┌─┴─╖ ┌───╖ │ │
│ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌───┤ + ║ │ ╘═╤═╝ ├──┴─┤ · ╟─┤ × ╟─┘ │
└───┤ └────┐ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ ╘═╤═╝ │
┌─┴─╖ ┌────╖ │ ║ 0 ╟─┤ ? ╟─┤ = ║ ┌┴┐ │ ║ 0 ╟─┤ ? ╟─┤ = ║ │ │ ┌────╖ │
│ × ╟─┤ << ╟─┘ ╚═══╝ ╘═╤═╝ ╘═╤═╝ └┬┘ │ ╚═══╝ ╘═╤═╝ ╘═╤═╝ │ └─┤ << ╟─┘
╘═╤═╝ ╘═╤══╝ ┌┐ ┌┐ │ │ └───┘ ┌─┴─╖ ├──────┘ ╘═╤══╝
│ └────┤├──┬──┤├─┘ ├─────────────────┤ · ╟───┘ │
│ └┘┌─┴─╖└┘ │ ┌┐ ┌┐ ╘═╤═╝ ┌┐ ┌┐ │
└────────────┤ · ╟─────────┘ ┌─┤├─┬─┤├─┐ └───┤├─┬─┤├────────────┘
╘═╤═╝ │ └┘ │ └┘ │ └┘ │ └┘
└───────────────┘ │ └────────────┘
最初のバージョンの説明
最初のバージョンでは、n = 7 を解決するのに約1時間かかりました。以下では、この低速バージョンがどのように機能するかについて主に説明します。下部では、10分以内に変更するためにどのような変更を行ったかを説明します。
ビットへの遠足
このプログラムにはビットが必要です。それには多くのビットが必要であり、それらをすべて適切な場所に必要とします。経験豊富なFuncitonプログラマーは、nビットが必要な場合、式を使用できることを既に知っています。
Funcitonでは次のように表現できます
パフォーマンスの最適化を行うと、次の式を使用して同じ値をはるかに高速に計算できることがわかりました。
この投稿の方程式のグラフィックスをすべて更新しなかったことをお許しください。
ここで、連続したビットブロックが必要ない場合を考えてみましょう。実際、次のように、k番目のビットごとに一定の間隔でnビットが必要です。
LSB
↓
00000010000001000000100000010000001
└──┬──┘
k
この式は、一度知っておくとかなり簡単です。
コードでは、関数Ӝ
は値nおよびkを取り、この式を計算します。
使用した番号を追跡する
あるN最終グリッド内の²の数字は、それぞれの数は任意とすることができるNの可能な値。番号は各セルで許可されたトラックを維持するために、我々は、以下からなる数維持のnビットが特定の値をとることを示すように設定されたビットを、³。最初、この数は明らかに0です。
アルゴリズムは右下隅から始まります。最初の数字が「推測」された後は0になりますが、同じ行、列、対角線に沿ったセルでは0が許可されなくなったという事実を追跡する必要があります。
LSB (example n=5)
↓
10000 00000 00000 00000 10000
00000 10000 00000 00000 10000
00000 00000 10000 00000 10000
00000 00000 00000 10000 10000
10000 10000 10000 10000 10000
↑
MSB
このために、次の4つの値を計算します。
現在の行:我々は必要Nビット毎のnビット目(セルごとに1つ)、および現在の行にそれを移行R、すべての行を記憶することは含まN ²ビット。
現在の列:n²ビットごとにnビット(行ごとに1つ)が必要です。次に、現在の列cにシフトします。すべての列にnビットが含まれることに注意してください。
前方対角線:nビットが必要です...(注意しましたか?すばやく見つけてください!)... n(n +1)番目のビット(よくやった!)前方対角線:
後方対角線: 2つのことがここにあります。最初に、後方の対角線にいるかどうかをどのようにして知るのでしょうか?数学的には、条件はc =(n − 1)− rであり、これはc = n +(− r − 1)と同じです。ねえ、それは何かを思い出させますか?そうです、2の補数なので、デクリメントの代わりにビットごとの否定(ファンシトンで非常に効率的)を使用できます。第二に、上記の式は最下位ビットを設定することを前提としていますが、逆方向の対角線では設定しないため、上にシフトする必要があります...ご存知ですか?...そうです、n(n − 1)。
これは、n = 1の場合に0で割る可能性がある唯一の方法でもあります。ただし、Funcitonは気にしません。0÷0はちょうど0です、わかりませんか?
コードでは、関数Җ
(一番下の関数)はnとインデックス(除算と剰余によるrとcの計算元)を取り、これらの4つの値を計算or
し、一緒にsします。
ブルートフォースアルゴリズム
ブルートフォースアルゴリズムはӁ
(上部の関数)によって実装されます。それはとりn個(グリッドサイズ)、インデックス(ここで、グリッドに我々は現在、数を配置している)、および撮影した(と数nはその数字我々は各セルの場所まだすることができ、私たちに言って³ビット)。
この関数は、文字列のシーケンスを返します。各文字列は、グリッドに対する完全なソリューションです。完全なソルバーです。許可するとすべてのソリューションが返されますが、遅延評価されたシーケンスとして返されます。
インデックスが0に達した場合、グリッド全体を正常に埋めたので、空の文字列(セルをカバーしない単一のソリューション)を含むシーケンスを返します。空の文字列はです0
。ライブラリ関数を使用して、それを⌑
単一要素のシーケンスに変換します。
以下のパフォーマンス改善で説明されているチェックがここで行われます。
インデックスがまだ0に達していない場合は、1をデクリメントして、数字を配置する必要があるインデックスを取得します(そのixを呼び出します)。
♫
0からn − 1の値を含む遅延シーケンスを生成するために使用します。
次にɓ
、以下を順番に実行するラムダで(モナドバインド)を使用します。
- 内の関連するビット初めて目に撮影した数は、ここか有効であるかどうかを決定します。私たちは、数置くことができ、私がしている場合にのみあれば取ら&(1 <<(N × IX)<< iは)すでに設定されていませんが。設定されている場合は、戻ります
0
(空のシーケンス)。
Җ
現在の行、列、および対角線に対応するビットを計算するために使用します。で、それをシフトI、その後、or
それが上に撮影しました。
Ӂ
残りのセルのすべてのソリューションを取得するために再帰的に呼び出し、新しい取得およびデクリメントされたixを渡します。これは、不完全な文字列のシーケンスを返します。各文字列にはix文字があります(グリッドはインデックスixまで埋められます)。
ɱ
(map)を使用して、こうして見つかったソリューションを調べ、iをそれぞれの末尾‼
に連結するために使用します。インデックスがnの倍数の場合は改行を追加し、そうでない場合はスペースを追加します。
結果を生成する
メインプログラムの呼び出しӁ
(ブルートの収納箱)とN、インデックス = N ²(我々は後方グリッドを埋める覚えている)と、撮影した = 0(最初は何も行われません)。この結果が空のシーケンス(ソリューションが見つからない)の場合、空の文字列を出力します。それ以外の場合は、シーケンスの最初の文字列を出力します。これは、シーケンスの最初の要素のみを評価することを意味することに注意してください。これが、ソルバーがすべての解を見つけるまで続行しない理由です。
パフォーマンスの改善
(すでに古いバージョンの説明を読んでいる人のために:プログラムは、出力用の文字列に個別に変換する必要があるシーケンスのシーケンスを生成しなくなりました。単に文字列のシーケンスを直接生成します。 。しかし、それは主な改善点ではありませんでした。
私のマシンでは、最初のバージョンのコンパイル済みexeがn = 7 を解決するのにほぼ1時間かかりました。これは、指定された10分の制限時間内ではなかったため、休みませんでした。(まあ、実際、私が休まなかった理由は、それを大幅に高速化する方法についてこのアイデアを持っていたからです。)
上記のアルゴリズムは、取得した番号のすべてのビットが設定されているセルに遭遇するたびに検索を停止し、バックトラックし、このセルには何も配置できないことを示します。
ただし、アルゴリズムは、これらすべてのビットが設定されているセルまでグリッドを無駄に埋め続けます。まだ入力されていないセルにすべてのビットが設定されたらすぐに停止できれば、はるかに高速になります。それ。しかし、すべてのセルを通過せずにセルのnビットが設定されているかどうかを効率的に確認するにはどうすればよいですか
トリックは、取得した数値にセルごとに1ビットを追加することから始まります。上に示したものの代わりに、次のようになります。
LSB (example n=5)
↓
10000 0 00000 0 00000 0 00000 0 10000 0
00000 0 10000 0 00000 0 00000 0 10000 0
00000 0 00000 0 10000 0 00000 0 10000 0
00000 0 00000 0 00000 0 10000 0 10000 0
10000 0 10000 0 10000 0 10000 0 10000 0
↑
MSB
代わりにN ³、そこに今あるN ²(N + 1)は、この数のビット。それに応じて、現在の行/列/対角に入力する関数が変更されました(実際、正直に言うと完全に書き直されました)。ただし、この関数はセルごとにnビットのみを設定するため、追加したばかりのビットは常にになります0
。
ここで、計算の途中1
で、中間のセルにa を配置しただけで、取得した数値が次のようになっているとします。
current
LSB column (example n=5)
↓ ↓
11111 0 10010 0 01101 0 11100 0 11101 0
00011 0 11110 0 01101 0 11101 0 11100 0
11111 0 11110 0[11101 0]11100 0 11100 0 ← current row
11111 0 11111 0 11111 0 11111 0 11111 0
11111 0 11111 0 11111 0 11111 0 11111 0
↑
MSB
ご覧のとおり、左上のセル(インデックス0)と左中のセル(インデックス10)は現在は不可能です。これをどのように最も効率的に決定するのでしょうか?
各セルの0番目のビットが設定されているが、現在のインデックスまでの数字を考えます。このような数値は、おなじみの式を使用して簡単に計算できます。
これらの2つの数値を加算するとどうなりますか?
LSB LSB
↓ ↓
11111 0 10010 0 01101 0 11100 0 11101 0 10000 0 10000 0 10000 0 10000 0 10000 0 ╓───╖
00011 0 11110 0 01101 0 11101 0 11100 0 ║ 10000 0 10000 0 10000 0 10000 0 10000 0 ║
11111 0 11110 0 11101 0 11100 0 11100 0 ═══╬═══ 10000 0 10000 0 00000 0 00000 0 00000 0 ═════ ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0 ║ 00000 0 00000 0 00000 0 00000 0 00000 0 ═════ ╨
11111 0 11111 0 11111 0 11111 0 11111 0 00000 0 00000 0 00000 0 00000 0 00000 0 o
↑ ↑
MSB MSB
結果は次のとおりです。
OMG
↓
00000[1]01010 0 11101 0 00010 0 00011 0
10011 0 00001 0 11101 0 00011 0 00010 0
═════ 00000[1]00001 0 00011 0 11100 0 11100 0
═════ 11111 0 11111 0 11111 0 11111 0 11111 0
11111 0 11111 0 11111 0 11111 0 11111 0
ご覧のとおり、そのセルのすべてのビットが設定されている場合にのみ、追加は追加した追加ビットにオーバーフローします!したがって、やるべきことは、これらのビットをマスクアウトして(上記と同じ式ですが、<< n)、結果が0かどうかを確認することです
00000[1]01010 0 11101 0 00010 0 00011 0 ╓╖ 00000 1 00000 1 00000 1 00000 1 00000 1 ╓─╖ ╓───╖
10011 0 00001 0 11101 0 00011 0 00010 0 ╓╜╙╖ 00000 1 00000 1 00000 1 00000 1 00000 1 ╓╜ ╙╖ ║
00000[1]00001 0 00011 0 11100 0 11100 0 ╙╥╥╜ 00000 1 00000 1 00000 0 00000 0 00000 0 ═════ ║ ║ ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0 ╓╜╙╥╜ 00000 0 00000 0 00000 0 00000 0 00000 0 ═════ ╙╖ ╓╜ ╨
11111 0 11111 0 11111 0 11111 0 11111 0 ╙──╨─ 00000 0 00000 0 00000 0 00000 0 00000 0 ╙─╜ o
ゼロでない場合、グリッドは不可能であり、停止できます。