数千の単語のリストと文字の小さなリストを使用して、単語の辞書がすべての文字をカバーしていると仮定して、与えられたすべての文字を利用するために最小量の単語を見つけようとしています。
最初のステップは、明らかに、文字を含まないすべての単語を削除することです。次に、残りの単語の各文字の相対的な希少性を計算し、長さに対する文字の相対的な希少性を組み合わせて単語を並べ替えることで、これにアプローチしました。したがって、残りのより一般的な文字を満足させることがますます容易になると仮定すると、これは比較的まれな文字の単語を最初に置きます。文字の「カバレッジ」が最も多い最良の単語を選択した後、文字のリストからそれらを削除し、すべての文字が満たされるまで、ループして残りの文字の単語の希少性を再計算します。
これは確かに機能しますが、これが理想的ではないことを示す奇妙な結果が得られることがあります。たとえば、5文字の要件でも、すべての文字をカバーする1つの最適な一致が見つかりますが、6番目の文字を追加すると、返される単語のシーケンスとまれなシフトが好ましくないため、突然4つの単語が返されます。
このアルゴリズムを改善するためにどのような戦略を使用できますか?また、私は現在、ループごとにすべての単語を数回繰り返し、希少性などを数えています。これは、膨大な辞書でますます高価になります。どんな提案も歓迎します:)
これよりも理想的な解決策があるかもしれませんが、それはあなたをより近づけると思います。現在、あなたのアルゴリズムは、「まれな」文字を含む単語を、文字のリストの全体的な範囲よりも重要であると評価しています。おそらく、使用する単語の数を減らす最善の方法は、まだ必要な文字が最も多い単語を選択することです。
たとえば、次の文字を一緒に含む単語の数を最小限に抑えようとしているとします。
Letters
--------
a
b
c
d
e
f
Words
--------
ace
bread
fork
whistle
crab
monkey
car
fork
dog
diamond
各単語がどれほど有用であるかを理解する簡単な方法は、各単語を見て、それに含まれる目的の文字を計算することです。これは、各単語のブールフラグの配列を使用して作成できます。
Words | a | b | c | d | e | f
---------------------------------
ace | T | F | T | F | T | F
bread | T | T | F | T | T | F
fear | T | F | F | F | T | T
whistle | F | F | F | F | T | F
crab | T | T | T | F | F | F
monkey | F | F | F | F | T | F
car | T | F | T | F | F | F
fork | F | F | F | F | F | T
dog | F | F | F | T | F | F
diamond | T | F | F | T | F | F
coffee | F | F | T | F | T | T
---------------------------------
Total | 6 | 2 | 4 | 3 | 6 | 3
まだ始まったばかりなので、すべてが必要です。それでは、まだ必要な文字数に基づいて、各単語にスコアを付けましょう。
Used Words:
Letters Remaining: a, b, c, d, e, f
Words | a | b | c | d | e | f | Score
-----------------------------------------
ace | T | F | T | F | T | F | 3
bread | T | T | F | T | T | F | 4
fear | T | F | F | F | T | T | 3
whistle | F | F | F | F | T | F | 1
crab | T | T | T | F | F | F | 3
monkey | F | F | F | F | T | F | 1
car | T | F | T | F | F | F | 2
fork | F | F | F | F | F | T | 1
dog | F | F | F | T | F | F | 1
diamond | T | F | F | T | F | F | 2
coffee | F | F | T | F | T | T | 3
それでは、最も多くの文字をノックアウトするものを取り上げましょう。この場合は「パン」です。つまり、使用する文字は「c、f」だけです。必要な文字に基づいて、各単語に新しいスコアを付けましょう。
Used Words: bread
Letters Remaining: c, f
Words | a | b | c | d | e | f | Score | Score 2
--------------------------------------------------
ace | T | F | T | F | T | F | 3 | 1
bread | T | T | F | T | T | F | 4 | -
fear | T | F | F | F | T | T | 1 | 1
whistle | F | F | F | F | T | F | 1 | 1
crab | T | T | T | F | F | F | 3 | 0
monkey | F | F | F | F | T | F | 1 | 1
car | T | F | T | F | F | F | 2 | 1
fork | F | F | F | F | F | T | 1 | 1
dog | F | F | F | T | F | F | 1 | 0
diamond | T | F | F | T | F | F | 2 | 0
coffee | F | F | T | F | T | T | 3 | 2
これで、スコアが2で、使用したい文字をすべて使い切った「コーヒー」を飲むことができます。明らかに、これにはいくつかの欠陥があります。まず、その後の選択は、最初の選択が何であったかによって異なります。一般的な文字がたくさんあるが、まれな文字はヒットしなかった単語を選んだ場合、多くの文字をヒットする1つの単語と、1つか2つしか得られない束になってしまう可能性があります。これは、より珍しい文字がより価値があるようにスコアを重み付けすることで解決できます(スクラブルをプレイするようなものです)。
お気づきかもしれませんが、パスごとにスコアを再計算する必要があります。そして、それは苦痛になるでしょう。しかし、それを簡単にするためにいくつかの巧妙なコンピューターのトリックを使用した場合はどうなりますか?各単語のブール値の配列の代わりに、それをビットのシーケンスに変換した場合はどうなりますか? (同じことですが、これを操作する方が簡単です。)
したがって、ビットとしてテーブルを書き直すと、次のようになります。
Words | Bits (a|b|c|d|e|f)
---------------------------------
ace | 101010
bread | 110110
fear | 100011
whistle | 000010
crab | 111000
monkey | 000010
car | 101000
fork | 000001
dog | 000100
diamond | 100100
coffee | 001011
スコアの計算は簡単になりますXOR必要な文字のマスクを使用して(最初は111111
、次に001001
)、真のビットを数えるだけです。非常に単純な再計算。
アルゴリズムに重みを付けたい場合は、実行は少し簡単になりますが、設定は難しくなります。各文字を数えて最も希少な文字を見つけた場合(最初の部分で行ったように)、ビットマスクをアルファベット順ではなく、希少性の順に並べることができます(最もまれなのは最初です)。この場合、ビットマスクは(b|d|f|c|a|e)
のようになります。次に、スコアの比較は、マスクを適用した後、最も高い整数で並べ替えるだけです。 (2つの文字が希少性のために結ばれている場合、これはいくつかの問題に遭遇することに注意してください。結果を歪める可能性があることに注意してください。単語のより大きなリストでこれが多く発生しないことを願っています。)
これは完璧ではありませんが、良いスタートです。
興味のない文字や単語をすべて捨ててから、各単語を含まれている興味のある文字のサブセットに減らすと、 集合被覆問題 になります。これはNP完全です。徹底的な検索を行いたくない場合は、未使用の文字が最も多いWordを繰り返し使用するのが最善の方法です。
辞書内の各単語を前処理して、その単語を一意の文字のリストにマップします。次に、希望の文字が含まれるマップエントリを探します。
Perlワンライナーの場合:
Perl -MList::Util=uniq -slne '
($Word = $_) =~ s/\W//g; # remove punctuation, etc
$key = join "", uniq sort split //, lc $Word;
Push @{$map{$key}}, $_;
} END {
print join "\n", @{$map{$letters}};
' -- -letters=aehilpst /usr/share/dict/words
philatelist
philatelist's
philatelists
shapeliest
slaphappiest
splashiest