スクラブルの文字の袋から特定の単語を描画する確率


18

タイルがあり、それぞれに文字が書かれたバッグがあるとします。あり文字'A'、とタイル 'B'で、というように、と 'ワイルドカード'タイルは、(私たちが持っている)。単語数が有限の辞書があるとします。nnAnBnn=nA+nB++nZ+n

交換せずにバッグからタイルを選びます。k

選択したタイルを指定した辞書から、長さ(1 < = <)の特定の単語を形成できる確率をどのように計算(または推定)しますか?llkk

Scrabble(TM)に慣れていない人には、ワイルドカード文字を使用して任意の文字と一致させることができます。したがって、単語「BOOT」は、タイル「B」、「*」、「O」、「T」で「スペル」できます。文字が描画される順序は重要ではありません。

提案:答えを書くのを簡単にするために、質問に答える方が良いかもしれません:新しいバッグから7文字を引いた後、可能な動きの中で「ブート」という単語を持っている確率はどれくらいですか?

(問題の概要は、この同様の質問からコピーされています


最初に、ワイルドカードのない単純なケースに取り組むことをお勧めします。
-Glen_b

@Glen_b同意します。私の最終目的は単語を確率順に並べることであるため、ワイルドカードを無視することは許容できる近似だと思います。ただし、この単純な問題を解決する式を作成するスキルはまだありません。
セバスチャン

1
さらに簡単に始めたい場合は、「B」、「O」、「O」、「T」を選択する確率を計算します。その後、任意の順序で文字を選ぶ確率を計算します。その後、7回試行することを考慮します。次に、ワイルドカードを考慮します。
ジェリーシルマー

1
この問題を解決する簡単な方法は、モンテカルロ近似を使用することです。これで十分でしょうか?
ラスマスバース

1
あなたが選んだ文字だけで単語を形成することについて話しているのですか、それともすでに選択されている文字とすでにボードに配置されている単語を考慮していますか?
-samthebrand

回答:


12

式が要求されます。残念ながら、状況は非常に複雑であるため、式はすべての可能性を列挙するための回り道に過ぎないようです。代わりに、この回答は、(a)二項係数の積の和を含む式に相当し、(b)多くのプラットフォームに移植できるアルゴリズムを提供します。


このような式を取得するには、可能性を 2つの方法で相互にばらばらのグループに分けます:ラックで選択された単語に含まれない文字の数(これを)と選択されるワイルドカード(空白)の数(これをwとします)。存在する場合、R = 7枚のラック内のタイル、N利用可能なタイル、Mはない単語の文字で利用可能なタイル、及びW = 2つの利用可能なブランク、によって与えられた可能な選択の数mはwがでありますmwr=7NMW=2mw

MmWwNMWrmw

なぜなら、非単語文字、空白、および単語文字の選択はm w r )に依存する独立した条件だからです。mwr

これは、単語の文字を表すタイルからのみ選択するときに単語のスペルをいくつかの方法を見つけることに問題を軽減 与えられていることのブランクが利用可能とされているR - M - wのタイルが選択されます。状況は乱雑であり、利用可能な閉じた式はありません。たとえば、w = 0のブランクとm = 3の単語外の文字が描画されると、「b」、「o」、および「t」のタイルから描画された「boot」を綴るために正確に4文字が残ります。 。所与ある2 'S "B" 8秒"O"'、及び6wrmww=0m=3286スクラブルタイルセットには「t」があり、「bboo」、「bbot」、「bbtt」、「booo」、「boot」、「bott」、「bttt」、「oooo」を描画(マルチセット)する肯定的な確率があります"、" ooot "、" oott "、" ottt "、および" tttt "ですが、これらのスペルの1つだけが" boot "です。そして、それは簡単なケースでした!たとえば、ラックに「o」、「b」、「t」のタイルからランダムに選択された5つのタイルと両方のブランクが含まれていると仮定すると、「ブート」をスペルする(スペルをしない)より多くの方法があります。たとえば、「boot」は「__boott」と「__bbttt」からスペルできますが、「__ ttttt」からはできません。

このカウント(問題の核心)は、再帰的に処理できます。 例で説明します。「b」、「o」、および「t」タイルのコレクションから1つの空白とさらに4つのタイルで「boot」のスペルの方法を数えたいとします(したがって、残りの2つのタイルは{ 「b」、「o」、「t」})。最初の文字「b」を考えてみましょう。

  1. 「b」は利用可能な2つの「b」タイルからの方法。これにより、空白と "o"および "t"タイルのコレクションから3つだけのタイルを使用して、接尾辞 "oot"のつづりの数を数える問題が軽減されます。21

  2. 1つのブランクを「b」として指定できます。これにより、残りの空白と「o」および「t」タイルのコレクションからさらに3つだけのタイルを使用して、「oot」のつづり方の数を数える問題が軽減されます。

一般に、手順(1)および(2)は互いに素であり、したがって確率の計算に追加的に貢献しますが、最初の文字に使用される可能性のある空白の数に対するループとして実装できます。削減された問題は再帰的に解決されます。基本的なケースは、1つの文字が残っていて、その文字が利用可能なタイルが一定数あり、ラック内にいくつかのブランクがある場合に発生します。ラック内のブランクの数に使用可能なタイルの数を足すだけで、最後の文字の必要な量を得るのに十分であることを確認するだけです。

ここでR再帰的ステップのためのコード。 rack通常はに等しく、文字のカウントの配列(など)、それらの文字で使用可能なタイルの数を与える同様の構造であり、ラック内にあると想定されるブランクの数です。7wordc(b=1, o=2, t=1)alphabetwild

f <- function(rack, word, alphabet, wild) {
  if (length(word) == 1) {
    return(ifelse(word > rack+wild, 0, choose(alphabet, rack)))
  }
  n <- word[1]
  if (n <= 0) return(0)
  m <- alphabet[1]
  x <- sapply(max(0, n-wild):min(m, rack), 
              function(i) {
                choose(m, i) * f(rack-i, word[-1], alphabet[-1], wild-max(0, n-i))
              })
  return(sum(x))
}

この関数へのインターフェイスは、標準のスクラブルタイルを指定し、指定された単語をそのマルチセットデータ構造に変換し、wで二重和を実行します。ここで二項係数 Mmwおよび WMmが計算され、乗算されます。Ww

scrabble <- function(sword, n.wild=2, rack=7, 
              alphabet=c(a=9,b=2,c=2,d=4,e=12,f=2,g=3,h=2,i=9,j=1,k=1,l=4,m=2,
                         n=6,o=8,p=2,q=1,r=6,s=4,t=6,u=4,v=2,w=2,x=1,y=2,z=1),
              N=sum(alphabet)+n.wild) {
  word = sort(table(strsplit(sword, NULL))) # Sorting speeds things a little
  a <- sapply(names(word), function(s) alphabet[s])
  names(a) <- names(word)
  x <- sapply(0:n.wild, function(w) {
    sapply(sum(word):rack-w, 
           function(i) {
             f(i, word, a, wild=w) *
               choose(n.wild, w) * choose(N-n.wild-sum(a), rack-w-i)
           })
  })
  return(list(numerator = sum(x), denominator = choose(N, rack),
              value=sum(x) / choose(N, rack)))
}

この解決策を試して、時間をかけてみましょう。次のテストでは、@ RasmusBååthによるシミュレーションで使用されたものと同じ入力を使用します

system.time(x <- sapply(c("boot", "red", "axe", "zoology"), scrabble))

このマシンは合計経過時間秒を報告します:かなり速いです。結果?0.05

> x
            boot        red         axe         zoology     
numerator   114327888   1249373480  823897928   11840       
denominator 16007560800 16007560800 16007560800 16007560800 
value       0.007142118 0.07804896  0.0514693   7.396505e-07

「ブート」の確率まさに値と等しい2381831 / 333490850はで得られた私の他の回答(記号代数コンピューティングプラットフォームを必要とする、より強力なフレームワークに類似した方法が、ソファ、それを使用しています)。すべての4つの単語の確率はかなり近い(原因のその低い確率に「動物学」の正確な値を与えることを期待することができませんでしたバースのシミュレーションにある1.184万/ 16007560800 あまり万人に1より)。114327888/160075608002381831/33349085011840/16007560800


クールでエレガントなソリューション!そして、私のものよりはるかに速い... :)
ラスマスバース

1
これは素晴らしい答えです、ありがとう。あなたのアルゴリズムをコーディングするのは大変だったので、すぐに使用できるコードは大歓迎です。私は知りませんでしたRが、1時間未満の作業で関数を使用することができたため、スクリプトは2万語の辞書ファイルから入力を取得し、結果を.csvに書き込みます。(ミッドレンジコアi5では10分もかかりませんでした)
セバスチャン

16

参照された質問への回答はここに直接適用されます。ターゲットワード(および可能なワイルドカードスペル)のみで構成される辞書を作成し、ランダムラックがターゲットを形成できない可能性を計算し、から減算します。この計算は高速です。1

シミュレーション(最後に表示)は、計算された回答をサポートします。


詳細

前の回答のように、Mathematicaは計算の実行に使用されます。

  1. 問題を指定します:単語(または必要に応じて単語)、文字、その数、およびラックサイズ。単語にないすべての文字は同じように動作するため、計算を大幅に高速化し、「単語にない任意の文字」を表す単一の記号に置き換えます。χ

    word = {b, o, o, t};
    letters = {b, o, t, \[Chi], \[Psi]};
    tileCounts = {2, 8, 6, 82, 2};
    rack = 7;
  2. この単語の辞書作成し、可能なすべてのワイルドカードスペルを含めるように拡張します。

    dict[words_, nWild_Integer] := Module[{wildcard, w},
       wildcard = {xx___, _, yy___} -> {xx, \[Psi], yy};
       w = Nest[Flatten[ReplaceList[#, wildcard] & /@ #, 1] &, words, nWild];
       Union[Times @@@ Join[w, Times @@@ words]]];
    dictionary = dict[{word}, 2]

    {bo2tbo2ψbotψo2tψboψ2o2ψ2btψ2otψ2}

  3. 非単語を計算する:

    alphabet = Plus @@ letters;
    nonwords = Nest[PolynomialMod[# alphabet, dictionary] &, 1, rack]

    b7+7b6o+21b5o2++7χψ6+ψ7

    185

  4. チャンスを計算します。 置換を伴うサンプリングの場合、変数をタイルカウントに置き換えるだけです。

    chances = (Transpose[{letters, tileCounts/(Plus @@ tileCounts)}] /. {a_, b_} -> a -> b);
    q = nonwords /. chances;
    1 - q

    20726341339062500000

    0.00756036.

    交換せずにサンプリングするには、べき乗の代わりに階乗を使用します。

    multiplicities = MapThread[Rule, {letters, tileCounts}];
    chance[m_] :=  (ReplaceRepeated[m , Power[xx_, n_] -> FactorialPower[xx, n]] 
                   /. multiplicities);
    histor = chance /@ MonomialList[nonwords];
    q0 = Plus @@ histor  / FactorialPower[Total[tiles], nn];
    1 - q0

    2381831333490850

    0.00714212.


シミュレーション結果

106

simulation = RandomChoice[tiles -> letters, {10^6, 7}];
u = Tally[Times @@@ simulation];
(p = Total[Cases[Join[{PolynomialMod[u[[All, 1]], dictionary]}\[Transpose], 
       u, 2], {0, _, a_} :> a]] / Length[simulation] ) // N

0.007438

それをその標準誤差に関連する計算値と比較します。

(p - (1 - q)) / Sqrt[q (1 - q) / Length[simulation]] // N

1.41259

合意は良好であり、計算結果を強力にサポートしています。

106

tilesAll = Flatten[MapThread[ConstantArray[#1, #2] &, {letters, tiles}] ]
    (p - (1 - q)) / Sqrt[q (1 - q) / Length[simulation]] // N;
simulation = Table[RandomSample[tilesAll, 7], {i, 1, 10^6}];
u = Tally[Times @@@ simulation];
(p0 = Total[Cases[Join[{PolynomialMod[u[[All, 1]], dictionary]}\[Transpose], 
       u, 2], {0, _, a_} :> a]] / Length[simulation] ) // N

0.00717

比較してください:

(p0 - (1 - q0)) / Sqrt[q0 (1 - q0) / Length[simulation]] // N

0.331106

このシミュレーションの一致は優れていました。

12


13

つまり、これはモンテカルロソリューションです。つまり、タイルの描画を何十億回もシミュレートし、これらのシミュレートされた描画のうち、指定された単語を形成できる結果になったものの数を計算します。ソリューションはRで作成しましたが、PythonやRubyなどの他のプログラミング言語を使用できます。

最初に、1つのドローをシミュレートする方法を説明します。まず、タイルの頻度を定義しましょう。

# The tile frequency used in English Scrabble, using "_" for blank.
tile_freq <- c(2, 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)
tile_names <- as.factor(c("_", letters))
tiles <- rep(tile_names, tile_freq)
## [1] _ _ a a a a a a a a a b b c c d d d d e e e e e e
## [26] e e e e e e f f g g g h h i i i i i i i i i j k l
## [51] l l l m m n n n n n n o o o o o o o o p p q r r r
## [76] r r r s s s s t t t t t t u u u u v v w w x y y z
## 27 Levels: _ a b c d e f g h i j k l m n o p q r ... z

次に、単語を文字カウントのベクトルとしてエンコードします。

word <- "boot"
# A vector of the counts of the letters in the word
word_vector <- table( factor(strsplit(word, "")[[1]], levels=tile_names))
## _ a b c d e f g h i j k l m n o p q r s t u v w x y z 
## 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 1 0 0 0 0 0 0 

7つのタイルのサンプルを描画し、単語と同じ方法でエンコードします。

tile_sample <- table(sample(tiles, size=7))
## _ a b c d e f g h i j k l m n o p q r s t u v w x y z 
## 1 0 0 0 0 1 0 0 0 0 0 0 1 0 1 1 0 0 0 0 0 1 0 1 0 0 0 

最後に、不足している文字を計算します...

missing <- word_vector - tile_sample
missing <- ifelse(missing < 0, 0, missing)
## _ a b c d e f g h i j k l m n o p q r s t u v w x y z 
## 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 

...不足している文字の数を合計し、使用可能な空白の数を減算します。結果がゼロ以下の場合、単語のスペルに成功しました。

sum(missing) - tile_sample["blank"] <= 0
## FALSE

この特定のケースでは、私たちはしませんでした...今、これを何度も繰り返し、成功したドローの割合を計算する必要があります。これはすべて、次のR関数によって実行されます。

word_prob <- function(word, reps = 50000) {
  tile_freq <- c(2, 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)
  tile_names <- as.factor(c("_", letters))
  tiles <- rep(tile_names, tile_freq)
  word_vector <- table( factor(strsplit(word, "")[[1]], levels=tile_names))
  successful_draws <- replicate(reps, {
    tile_sample <- table(sample(tiles, size=7))
    missing <- word_vector - tile_sample
    missing <- ifelse(missing < 0, 0, missing)
    sum(missing) - tile_sample["_"] <= 0
  })
  mean(successful_draws)
}

ここでreps描くシミュレートの数です。これで、さまざまな単語を試してみることができます。

> word_prob("boot")
[1] 0.0072
> word_prob("red")
[1] 0.07716
> word_prob("axe")
[1] 0.05088
> word_prob("zoology")
[1] 2e-05

さまざまな答えが得られます。シミュレーションコードの複雑さを考えると、なぜ意見が合わないのかを説明するのは難しいですが、ワイルドカードの処理で原因の検索を開始します。
whuber

2
私はそれsampleがあなたが期待するように動作しないと信じています。たとえば、28タイルのラックを許可するようにゲームが変更された場合、コードはどうなりますか?を見つけるために変更size=7size=28ます。
whuber

2
@whuberそうですね、指摘してくれてありがとう!これで動作し、コードと同じ答えが得られます!
ラスマスバース

この素晴らしい仕事をありがとう。実際、モンテカルロ法が完全に適しています。ただし、主にパフォーマンス上の理由から、whuberが提供する正確な計算アルゴリズムを使用することにしました。
セバスチャン

7

p0=nb1no2nt1n43n7
pkk
p0=nb1no2nt1n43n7p1=p0+n1no2nt1n43n7+nb1no1n1nt1n43n7+nb1no2n1n43n7=p0+n1n43n7no2nt1+nb1no1nt1+nb1no2p2=p1+n2n43n7nb1no1+nb1nt1+no2+no1nt1p3=p2+n3n43n7nb1+no1+nt1p4=p3+n4n43n7p=p44

n

1
p0n=1008/25850.0031

-1

えー

γc=b0バツclnバツr=0c+y1c+αrc+βrc+1rc+γrバツr+

+b0バツcr=0c+γ1c+αrc+βrc+1rc+γr1c+γ1+

+k=0r11c+α+κ+1c+β+κ+1c+1+κ1c+γ+κバツr

=b0バツcr=0c+γ1c+αrc+βrc+1rc+γrln バツ+1c+γ1+

+k=0r11c+α+κ+1c+β+κ1c+1+κ1c+γ+κバツr

私が自分のプロジェクトをどのように構築したかを見てからしばらく経ちました。そして、私の数学は以下で完全に間違っているか、正しいかもしれません。私はそれを後方に持っているかもしれません。正直なところ、私は忘れています。だが!空白のタイルを考慮せずに二項式の組み合わせのみを使用すると、全体が破壊されます。ワイルドのないシンプルな組み合わせソリューション。

私は自分でこれらの質問をし、そのために独自のスクラブル単語確率辞書を作成しました。あなたは引き出した可能性のある単語の辞書を必要とせず、その背後にある数学とタイルバッグの中の文字に基づいた利用可能な文字だけが必要です。英語の規則の配列は以下のとおりです。ゲームで使用できないすべての英語の単語(ゲームでは使用できない単語を含む)についてこの質問に答えるために、数週間かけて数学を開発しました。すべて間違っている可能性があります。

Scrabbleの文字の袋から特定の単語を描画する確率は、各文字(AZ)ごとに袋にある文字の数と、ワイルドカードを数学の追加として使用しているかどうかを必要とします。空白のタイルはこの計算に含まれています-100個のタイルのうち2個が空白であると仮定します。また、使用可能なタイルの数は、ゲームの言語や世界中のゲームルールによって異なります。英語のスクラブルはアラビア語のスクラブルとは明らかに異なります。使用可能な文字を変更するだけで、数学が作業を行うはずです。

誰かがエラーを見つけた場合は、必ず更新して解決します。

ブート:スクラブルのゲームにおけるブートの確率は0.000386%で、ブートのワードページに示されているように、173,758ハンド中67のチャンスです。

英語のタイル

すべてがバッグの中の文字の配列です。countはそのレターで使用可能なタイルの配列であり、pointはレターのポイント値です。

// All arranged by letter, number of letters in scrabble game, and point for the letter.
$all = array("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z");
    $count = array("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");
$point = array("1", "3", "3", "2", "1", "4", "2", "4", "1", "8", "5", "1", "3", "1", "1", "3", "10", "1", "1", "1", "1", "4", "4", "8", "4", "10");

英語のスクラブルゲームには100個のタイルがあります(つまり、合計$count)。タイルがどのように引っ張られるかは問題ではないため、順列ではありません。

使用した数学 単語に含まれる文字の数と単語に含まれる文字の数、タイルバッグで使用できる文字の数(各文字、一意、およびallcharsごとにカウント)を決定します。長さの二項係数で除算されたそれぞれの二項係数。

利用可能な二項組み合わせを決定する

let C(n,r) be binomial coefficient: n!/[n!(n-r)!], or 0 if r > n

Foreach文字、二項係数は何ですか。

1つの「B」があります。2が利用可能で、bを引く確率は2%です。
2つの「O」があります。8つありますが、8%の確率でoを引きます。
1つの「T」があります。使用可能な6個があり、6%の確率でtがプルされます。
BOOTは4文字の単語で、100タイルセットから空白を取り、98タイルなしから取ります。

n =98。英語セットの空白のないタイルの数

B=21=2221
O=82=8882
T=61=6661

B×O×T tilecountの二項係数で除算 989898length


何を知らずにソリューションを評価するのは難しい n そして r最終式で参照してください。空白のタイルの効果をどのように処理しますか?それがこれを難しい問題にしているのです。とにかく、その価値が38248840160075608000.00239間違っています:これは、R私が投稿したソリューションを使用して取得されました。この1秒お試しくださいR:シミュレーションlet <- c(rep("b", 2), rep("o", 8), rep("t", 6), rep("_", 84)); boot <- function(x) sum(x=="b")>=1 && sum(x=="o")>=2 && sum(x=="t")>=1; mean(replicate(1e5, boot(sample(let, 7))))
whuber

編集を再:明らかなエラーの1つは、計算が空白の数をまったく考慮しないことです。あなたの式から私が知る限り、その数が(2から50まで、たとえば)変わっても、答えは変わりません。それは明らかに間違っています。あなたが直面するもう一つの問題は、あなたの答えがすでに投稿された他の3つの答えとどのように衝突するかを説明することです。
whuber

組み合わせの場合-数学は二項係数です。したがって、xを空白タイルの数とします。変化する唯一の数学はnです!-空白が使用されているかどうか。その場合、nに空白のカウントを追加します!空白は可能なすべての文字(n + x)の2つのオプションを許可するためです!-そうでない場合は、nのままにしてください!そのまま。はい?番号?この場合に設定された言語ルールに応じて空白が使用されない場合、英語!n!= 98または100 with。空白のない各文字はC(n、r)、それ以外は空白のC((n + x)、r)です。配列には空白がありますが、数学に空白を入れるのを忘れていました。したがって、nを変更してブランクを処理します。はい?
ジェームズコルデイロ14年

いいえ、あなたの推論は無効です。小さい数字で数式を試してみて、どこで間違っているのかを確認してください。
whuber

数字が小さいとはどういう意味ですか?例を挙げてください。代わりに、10個の文字、1個のb、2個のo、1個の空白を含む1個のt、および5個の他の文字のセットからブーツを引くと言っていますか。または完全に異なるもの。私は数学を専攻していませんが、ポーカープレイヤーになったようです。現在、スーツのないスクラブルタイルのポーカーオッズを計算しています。
ジェームズコルデイロ14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.