マルセルプルーストとマルコフ解読セキュリティサービスのT9テキスト


11

この挑戦は、もう精神的にPythonesqueであるかのように... マルコフ連鎖や暗号化技術の予備知識は必要ありません。

あなたはイギリスの警備サービスM1Sから重要な情報を入手する必要があるスパイです。M1Sのエージェントは、Wi-Fi信号が傍受される可能性があること、Android / iOSのセキュリティ脆弱性が悪用されることなどを十分に認識しているため、全員がNokia 3310を使用して、T9自動補完を使用して入力されるテキスト情報を送信しています。

あなたは以前に電話をハッキングしてagency報機関に配送し、キーロガーを素晴らしいプラスチックキーボードの下にインストールしていたので、入力した文字に対応する一連の数字を受け取るので、「ワシは巣から離れてエージェントに警告します

84303245304270533808430637802537808430243687

ちょっと待って!一部のT9シーケンスはあいまいです(「6263」は「name」、「mane」、または「oboe」になる可能性があります。不明瞭になるほど、疑わしくなります!)、どうしますか?M1Sが使用する入学試験は、マルセルプルーストの傑作「過去の記憶」を15秒で要約することだけなので、chef-d全体の頻度分布に従って、前の単語の次に来る単語を選択します。プルーストの傑作!

コードを解読して、元のメッセージを入手できますか?

T9の原理

エージェントが使用するNokia 3310キーボード

T9自動補完メカニズムは、次のように説明できます。上の図に示すように、アルファベット文字を数字にマップします。

abc     -> 2
def     -> 3
ghi     -> 4
jkl     -> 5
mno     -> 6
pqrs    -> 7
tuv     -> 8
wxyz    -> 9
<space> -> 0
<other> -> <is deleted>

T9復号化装置は一連の数字を受け取り、それらのキーを押して入力できる単語を推測しようとします。標準の頻度表を使用することもありますが、さらに一歩進んで、マルコフ連鎖を使用して次の単語を予測しています!

学習サンプル

コーパスは、プルーストの「物事過去の思い出」のこの重く取り除かバージョンs/-/ /gs/['’]s //gおよびs/[^a-zA-Z ]//g-仰せられた混乱所有格's!)もともとに掲載さアデレード大学のウェブサイト(この作品のテキストは、オーストラリアでのパブリックドメインです)。

テキスト全体が一つの長い単語のベクトルとして、一つの長い文として、1つの文字列として分析する必要があります(あなたの言語のためのより便利な方)、改行が取り除かれ、スペースで単語に分割。(単一のパラグラフファイルは、githubツールによって眉をひそめられる可能性があるため、提供していません。)

テキスト全体を1つの文字列/文として読み取るにはどうすればよいですか?Rの例:

p_raw  <- read.table("proust.txt", sep="\t") # Because there are no tabs
p_vec  <- as.character(p_raw$V1)       # Conversion to character vector
p_str  <- paste(p_vec, collapse=" ")   # One long string with spaces
p_spl  <- strsplit(p_str, split=" ")[[1]] # Vector of 1360883 words
proust <- p_spl[p_spl!=""]           # Remove empty entries — 1360797

仕事

数字のシーケンスを数値として指定すると、確率チェーンを使用して対応するT9キーを使用して入力できるテキスト文字列を返し、1つの長い文として扱われるこのトレーニングテキストに基づいて次の単語Xを予測します

場合Xは、テキストの最初のT9ワードであり、複数の推測があり、そうでない可能性だけを選んで、ランダムに1つ選択。

すでに解読された単語w(i-1)が先行する後続のすべてのT9単語X(i )について

  1. T9ワードXを1つのユニークな方法で通常のワードxに変換できる場合、それを行います。
  2. Xに対して複数の変換オプション(たとえばx1、x2、...)が利用可能な場合、前に推測された単語wを検索します。
    • Proustの元の作品でwの後にXにマッピングされるものが続かない場合は、可能なx1、x2、...のいずれかをランダムに選択します。
    • 場合XはWいつもに対応ワット×1元にして何の同時存在しないXIにマップすることができさんXは、選ん×1
    • 場合はXをWに変換することができワット×1 ワット×2 、それはコーパスで見つけることができます...、そして、すべての可能なカウントXIをあのフォローをwをしてにマッピングXコーパスにして選ぶXIを確率でXI /(X1 + X2 + ...)

例2a。メッセージがある場合76630489、ここで489あり得るguyか、ivy(それらが少なくとも一度コーパスに起こる)、7663のように解読することができるsome(非常に有望な最初の単語)。場合someにマップするものが続くことはありません489コーパスでは、その後、選択guyまたはivy確率0.5でランダムに。

例2b。メッセージがあるなら766302277437、どこに2277437あるbarriercarrier、または7663として解読できますsome。プルーストが常に使用しsome carrier、使用しないsome barrier場合は、選択しsome carrierます。

例2c。シーケンスを解読するとします5363076635363として予測されたlend7663次のいずれかになります:pondroofおよびsomelendサンプルコーパス内の後続の単語の出現回数をカウントします。次のようなものが得られると仮定します(説明のためだけに):

        T9  Word following lend  Occurrences
      7663  some                           7
      7663  pond                           2
      7663  roof                           1

もしそう7663で先行されlend、そこにある7/(7+2+1)=70%確率7663を意味しsome、20%pondと10% roof。アルゴリズムはlend some、70%の場合、lend pond20%の場合などで生成する必要があります。

エージェントはa〜zの文字とスペースのみを使用していると安全に想定できます(句読点、所有格's、数字は使用していません)。

また、M1Sのエージェントは、「過去の記憶」の範囲外の言葉(29,237語の途方もないボキャブラリーです!)を使用しないと想定することもできます

T9機能はこのチャレンジで実装されたので、ご覧ください。

助けが必要な場合、確率的連鎖はこれそれ、および以下の課題で見事に飼いならされましたがそのような連鎖の原理を知る必要さえありません:すべてがタスクで述べられています。

テストケース

--Inputs--
20784250276960369
20784250276960369
84303245304270533808430637802537808430243687
94280343084306289072908608430262780482737
94280343084306289072908608430262780482737

--Possible outputs--
c quick brown fox
a stick crown fox
the eagle gas left the nest blest vie agents
what did the navy pay to the coast guards
what did the navy raz un the coast guards

ルール:

  • 標準の抜け穴が適用されます。
  • 元のメッセージがわからないため、一連の数字とproust.txtファイルだけをメモリ/ワークスペース/何でもロードする必要があります。自己完結型のものを用意する必要はありません。proust.txt常にアクセス可能であると仮定します。
  • コーパスに従って複数の復号化オプションが考えられる場合、アルゴリズムはそれぞれの確率で異なる出力を生成できる必要があります(例2cを参照)。

できるだけ目立たないようにする必要があるため、最短のコードが優先されます!

PSこの確率的アルゴリズムの明らかな利点は、あいまいな解読された文字列に対して真の元の文字列を取得する確率が1になる傾向があるという事実です。

PPS 部分一致による予測 も参照してください。


サンドボックスからのピーターテイラーの発言が考慮されました。悲しいことに、複数の更新にもかかわらず、投稿された週に回答した人はほとんどいなかったので、提案は大歓迎です!ところで、これは私の最初の挑戦です!
アンドレイKostyrka

あなたが多くの反応を得なかった大きな理由は、この問題を理解するために必要な高度な知識のためだと思います。この課題を大勢の人々にアピールしたい場合、仕事中のマルコフチェーンを示す初期の例を含めることをお勧めします:)
ネイサンメリル

@NathanMerrill OK、サンプルのチャレンジに3つのリンクを追加しました。ただし、ユーザーはマルコフ連鎖をまったく知る必要はありません。なぜなら、タスクは可能な限りアルゴリズムで質問本文に記述されているからです。私は自己の十分な、それはにつれてとしてそれを作ってみました...
アンドレイKostyrka

ああ、分かりました。あなたがそれを説明しなければ、私はそれを閉じることに投票したでしょう。それはちょうど見え、それは:)高度な知識を必要とするように
ネイサンメリル

1
私はこの挑戦が好きですが、まだ座って解決策を作成/ゴルフする時間がありませんでした。うまくいけば、それはすぐに起こります。
Mego

回答:


1

Rソリューション、できることの非競合的な例

まず、単語のシーケンスをメモリにロードします。

p_raw  <- read.table("proust.txt", sep="\t") # Because there are no tabs
p_vec  <- as.character(p_raw$V1)       # Conversion to character vector
p_str  <- paste(p_vec, collapse=" ")   # One long string with spaces
p_spl  <- strsplit(p_str, split=" ")[[1]] # Vector of 1360883 words
proust <- p_spl[p_spl!=""]           # Remove empty entries — 1360797

次に、テキストをT9で識別する関数が必要です。

t9 <- function (x) {
  x <- chartr(paste(c(letters, " "), collapse=""), "222333444555666777788899990", tolower(x))
  x <- gsub("[^0-9]", "", x, perl = TRUE) # Safety check
  x <- x[x!=""] # Also for safety because... you know...
  x
}

次に、プルーストをT9化します。

p9 <- t9(proust)

最終準備:呼び出す関数を使用して、入力文字列をゼロで分割しますprep)。

prep <- function (x) {
  x <- chartr("0", " ", x)
  x <- strsplit(x, " ")[[1]]
  x <- x[x!=""] # Boil the empty strings for safety
  x
}

そして今、私は数字の入力文字列を取り、prepそれをs、単語を一つずつ解読する関数を提案します:

decip <- function(x, verbose = FALSE) {
  x <- prep(x)
  l <- length(x)
  decrypted <- rep(NA, l)
  tb <- table(proust[which(p9 == x[1])])
  decrypted[1] <- sample(names(tb), 1, prob=tb/sum(tb))
  if (verbose) print(decrypted[1])
  for (i in 2:l) {
    mtchl <- p9 == x[i]
    mtch <- which(mtchl)  # Positions that matched
    pmtch <- proust[mtch] # Words that matched
    tb <- table(pmtch)    # Count occurrences that matched
    if (length(tb)==1) {  # It is either 1 or >1
      decrypted[i] <- names(tb)[1]
      if (verbose) print(paste0("i = ", i, ", case 1: unique decryption"))
      } else {  # If there are more than one ways to decipher...
      preced <- proust[mtch-1] 
      s <- sum(preced==decrypted[i-1])
      if (s==0) {
        decrypted[i] <- sample(names(tb), 1)
        if (verbose) print(paste0("i = ", i, ", case 2a: multiple decryption, collocation never used, picking at random"))
        } else {
        tb2 <- table(pmtch[preced==decrypted[i-1]])
        if (length(tb2)==1) {
          decrypted[i] <-  names(tb2)[1]
          if (verbose) print(paste0("i = ", i, ", case 2b: multiple decryption, only one collocation found, using it"))
        } else {
          decrypted[i] <- sample(names(tb2), 1, prob = tb2/sum(tb2))
          if (verbose) print(paste0("i = ", i, ", case 2c: multiple decryption, ", length(tb2), " choices"))
          }
      }
    }
    if(verbose) print(decrypted[i])
  }
  decrypted
}

そして今、実際に何をしているのか:

decip("20784250276960369", verbose=TRUE)
----
[1] "a"
[1] "i = 2, case 2c: multiple decryption, 2 choices"
[1] "quick"
[1] "i = 3, case 2a: multiple decryption, collocation never used, picking at random"
[1] "brown"
[1] "i = 4, case 1: unique decryption"
[1] "fox"
[1] "a"     "quick" "brown" "fox" 

2番目の例:

decip("84303245304270533808430637802537808430243687", verbose=TRUE)
----
[1] "what"
[1] "i = 2, case 2b: multiple decryption, only one collocation found, using it"
[1] "did"
[1] "i = 3, case 2b: multiple decryption, only one collocation found, using it"
[1] "the"
[1] "i = 4, case 1: unique decryption"
[1] "navy"
[1] "i = 5, case 2a: multiple decryption, collocation never used, picking at random"
[1] "raz"
[1] "i = 6, case 2a: multiple decryption, collocation never used, picking at random"
[1] "um"
[1] "i = 7, case 2a: multiple decryption, collocation never used, picking at random"
[1] "the"
[1] "i = 8, case 2b: multiple decryption, only one collocation found, using it"
[1] "coast"
[1] "i = 9, case 1: unique decryption"
[1] "guards"
[1] "what"   "did"    "the"    "navy"   "raz"    "um"     "the"    "coast"  "guards"

これはゴルフができることをコメントしないでください。私のひどい冗長さのためにこの挑戦に興味を持っている人はほとんどいないようですので、私はこの答えを投稿して、可能なプログラムがどのように見えるかを示しました。この回答に賛成/反対投票する必要はありません。


1

Python 3、316バイト

from random import*
from collections import*
def d(s,f):
 D=defaultdict(Counter);p=q=''
 for w in open(f).read().split():D[w.translate({97+c:(c-(c>17)-(c>24))//3+50for c in range(26)})].update([w]);D[p].update([w]);p=w
 for c in s.split('0'):q=choice([*(len(D[c])>1and D[c]&D[q]or D[c]).elements()]);print(q,end=' ')
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.