Scrabbleおよびその変形で有効な単語を含まないラックを描くのは非常に困難です。以下は、最初の7タイルラックに有効な単語が含まれていない確率を推定するために書いたRプログラムです。モンテカルロアプローチとWords With Friendsレキシコンを使用します(公式のScrabbleレキシコンが簡単な形式で見つかりませんでした)。各トライアルでは、7タイルラックを描画し、ラックに有効な単語が含まれているかどうかを確認します。
最小限の言葉
ラックに有効な単語が含まれているかどうかを確認するためにレキシコン全体をスキャンする必要はありません。最小限の単語で構成される
最小限の辞書をスキャンするだけです。単語がサブセットとして他の単語を含まない場合、単語は最小です。たとえば、「em」は最小限の単語です。「空」ではありません。この点は、ラックに単語xが含まれている場合、xのサブセットも含まれている必要があるということです。言い換えると、最小限の単語が含まれていない場合、ラックには単語が含まれていません。幸いなことに、レキシコンのほとんどの単語は最小限ではないため、削除できます。順列に相当する単語をマージすることもできます。Words With Friendsのレキシコンを172,820から201の最小単語に減らすことができました。
ワイルドカードは、ラックや単語を文字の分布として扱うことで簡単に処理できます。ある分布から他の分布を引くことにより、ラックに単語が含まれているかどうかを確認します。これにより、ラックにない各文字の数がわかります。これらの数の合計がある場合ワイルドカードの数は、その単語は、ラックにあります。≤
モンテカルロアプローチの唯一の問題は、興味のあるイベントが非常にまれであることです。したがって、十分に小さな標準誤差で推定値を得るには、多くの試行が必要です。私は(一番下に貼り付けられた)私のプログラムを実行した
試練となった最初のラックが有効な単語が含まれていないことを0.004の推定確率を。その推定の推定標準誤差は0.0002です。辞書をダウンロードするなど、Mac Proで実行するのに数分かかりました。N= 100 、000
誰かが効率的な正確なアルゴリズムを思い付くことができるかどうかに興味があります。包含/除外に基づく単純なアプローチは、組み合わせ爆発を伴う可能性があるようです。
包含除外
これは悪い解決策だと思いますが、とにかく不完全なスケッチです。原則として、計算を行うプログラムを作成できますが、仕様は曲がりくねります。
計算する確率は
右側の確率内部イベントは、イベントの和集合である:
P (K ラック-tile単語を含んでいる)= P (∪ X ∈ M { K ラック-tileに含まれる Xを} )、M
P(k-tile rack does not contain a word)=1−P(k-tile rack contains a word).
P(k-tile rack contains a word)=P(∪x∈M{k-tile rack contains x}),
Mは最小限の辞書です。包含/除外式を使用して拡張できます。上記のイベントのすべての可能な交差点を考慮する必要があります。レッツ
のパワーセット表す
Mすなわち、すべての可能なサブセットの集合
Mを。それから
P(M)MMP(k-tile rack contains a word)=P(∪x∈M{k-tile rack contains x})=∑j=1|M|(−1)j−1∑S∈P(M):|S|=jP(∩バツ∈S{ K -tileラックが含ま Xを} )
最後に指定するのは、上の最後の行で確率を計算する方法です。多変量超幾何が含まれます。
ラック内のすべての単語含まれている場合であるSを。これは、ワイルドカードのために対処するのが苦痛です。条件付けにより、次の各ケースを考慮する必要があります:ラックにワイルドカードが含まれていない、ラックにワイルドカードが1つ含まれている、ラックにワイルドカードが2つ含まれている、...
∩X ∈ S{ k タイルラックにx が含まれる }
S
それから
P(∩X ∈ S{ K -tileラックが含ま Xを} )= ∑w = 0n∗P(∩X ∈ S{ k タイルラックにx が含まれる } | k タイルラックにはw ワイルドカードが含まれます )× P(k タイルラックにはw ワイルドカードが含まれます )。
2| M|2| M|≈ 3.2 × 1060
考えられるすべてのラックをスキャンする
最小単語の可能なサブセットよりも可能なラックが少ないため、これは計算が簡単だと思います。可能な集合を連続的に減らすk-単語が含まれていないラックのセットを取得するまで、タイルラック。Scrabble(またはWords with Friends)の場合、可能な7タイルラックの数は数百億です。可能性のある単語を含まないものの数を数えるには、数十行のRコードで実行できるはずです。しかし、考えられるすべてのラックを列挙するだけでなく、もっとうまくやれるはずだと思います。たとえば、「aa」は最小限の単語です。これにより、複数の「a」を含むすべてのラックがすぐに削除されます。他の言葉で繰り返すことができます。最新のコンピューターでは、メモリーは問題になりません。7タイルのスクラブルラックに必要なストレージは7バイト未満です。最悪の場合、可能なすべてのラックを格納するために数ギガバイトを使用しますが、それも良い考えだとは思いません。誰かがこれについてもっと考えたいかもしれません。
モンテカルロRプログラム
#
# scrabble.R
#
# Created by Vincent Vu on 2011-01-07.
# Copyright 2011 Vincent Vu. All rights reserved.
#
# The Words With Friends lexicon
# http://code.google.com/p/dotnetperls-controls/downloads/detail?name=enable1.txt&can=2&q=
url <- 'http://dotnetperls-controls.googlecode.com/files/enable1.txt'
lexicon <- scan(url, what=character())
# Words With Friends
letters <- c(unlist(strsplit('abcdefghijklmnopqrstuvwxyz', NULL)), '?')
tiles <- c(9, 2, 2, 5, 13, 2, 3, 4, 8, 1, 1, 4, 2, 5, 8, 2, 1, 6, 5, 7, 4,
2, 2, 1, 2, 1, 2)
names(tiles) <- letters
# Scrabble
# tiles <- c(9, 2, 2, 4, 12, 2, 3, 2, 9, 1, 1, 4, 2, 6, 8, 2, 1, 6, 4, 6, 4,
# 2, 2, 1, 2, 1, 2)
# Reduce to permutation equivalent words
sort.letters.in.words <- function(x) {
sapply(lapply(strsplit(x, NULL), sort), paste, collapse='')
}
min.dict <- unique(sort.letters.in.words(lexicon))
min.dict.length <- nchar(min.dict)
# Find all minimal words of length k by elimination
# This is held constant across iterations:
# All words in min.dict contain no other words of length k or smaller
k <- 1
while(k < max(min.dict.length))
{
# List all k-letter words in min.dict
k.letter.words <- min.dict[min.dict.length == k]
# Find words in min.dict of length > k that contain a k-letter word
for(w in k.letter.words)
{
# Create a regexp pattern
makepattern <- function(x) {
paste('.*', paste(unlist(strsplit(x, NULL)), '.*', sep='', collapse=''),
sep='')
}
p <- paste('.*',
paste(unlist(strsplit(w, NULL)),
'.*', sep='', collapse=''),
sep='')
# Eliminate words of length > k that are not minimal
eliminate <- grepl(p, min.dict) & min.dict.length > k
min.dict <- min.dict[!eliminate]
min.dict.length <- min.dict.length[!eliminate]
}
k <- k + 1
}
# Converts a word into a letter distribution
letter.dist <- function(w, l=letters) {
d <- lapply(strsplit(w, NULL), factor, levels=l)
names(d) <- w
d <- lapply(d, table)
return(d)
}
# Sample N racks of k tiles
N <- 1e5
k <- 7
rack <- replicate(N,
paste(sample(names(tiles), size=k, prob=tiles),
collapse=''))
contains.word <- function(rack.dist, lex.dist)
{
# For each word in the lexicon, subtract the rack distribution from the
# letter distribution of the word. Positive results correspond to the
# number of each letter that the rack is missing.
y <- sweep(lex.dist, 1, rack.dist)
# If the total number of missing letters is smaller than the number of
# wildcards in the rack, then the rack contains that word
any(colSums(pmax(y,0)) <= rack.dist[names(rack.dist) == '?'])
}
# Convert rack and min.dict into letter distributions
min.dict.dist <- letter.dist(min.dict)
min.dict.dist <- do.call(cbind, min.dict.dist)
rack.dist <- letter.dist(rack, l=letters)
# Determine if each rack contains a valid word
x <- sapply(rack.dist, contains.word, lex.dist=min.dict.dist)
message("Estimate (and SE) of probability of no words based on ",
N, " trials:")
message(signif(1-mean(x)), " (", signif(sd(x) / sqrt(N)), ")")