Marcel Proust และ Markov ถอดรหัสข้อความ T9 ของบริการความปลอดภัย


11

ราวกับว่าความท้าทายนี้อาจเป็นPythonesqueอีกต่อไปในจิตวิญญาณอีกต่อไป ... ไม่จำเป็นต้องมีความรู้มาก่อนเกี่ยวกับโซ่มาร์คอฟหรือเทคนิคการเข้ารหัส

คุณเป็นสายลับที่ต้องการได้รับข้อมูลสำคัญจากบริการรักษาความปลอดภัยของอังกฤษ M1S ตัวแทนของ M1s ตระหนักดีว่าสัญญาณ Wi-Fi ของพวกเขาสามารถดัก, Android / iOS ช่องโหว่ความปลอดภัยของพวกเขาใช้ประโยชน์อื่น ๆ ดังนั้นทั้งหมดของพวกเขาใช้ Nokia 3310 ของข้อมูลข้อความส่งที่ถูกพิมพ์โดยใช้T9 อัตโนมัติเสร็จ

ก่อนหน้านี้คุณเคยแฮ็กโทรศัพท์ที่จะส่งไปยังสำนักข่าวกรองและได้ติดตั้งคีย์ล็อกเกอร์ภายใต้คีย์บอร์ดพลาสติกอันทรงเกียรติดังนั้นตอนนี้คุณจะได้รับหมายเลขที่ตรงกับตัวอักษรที่พิมพ์ดังนั้น " นกอินทรีจึงออกจากรังเพื่อเตือนตัวแทน "

84303245304270533808430637802537808430243687

แต่เดี๋ยวก่อน! ลำดับ T9 บางอย่างไม่ชัดเจน (“ 6263” อาจเป็น“ ชื่อ”,“ แผงคอ” หรือ“ โอโบ”; ยิ่งคลุมเครือยิ่งสงสัยมากขึ้น!) คุณจะทำอย่างไร คุณรู้ว่าการสอบเข้าครั้งแรกที่ใช้คือการสรุปผลงานชิ้นเอกของ Marcel Proust“ Remembrance of Things Past” ในเวลา 15 วินาทีดังนั้นคุณต้องการเลือกคำที่มาถัดจากคำก่อนหน้านี้ตามการกระจายความถี่ใน Chef-d ' œuvre of Proust!

คุณสามารถถอดรหัสรหัสและรับข้อความเดิมได้หรือไม่?

หลักการของ 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/-/ /gโดดเด่นอย่างมากของ "ความทรงจำของสิ่งที่ผ่านมา" ของ Proust ( s/['’]s //gและs/[^a-zA-Z ]//g- ทำให้เกิดความสับสนในการครอบครอง's!) เผยแพร่ครั้งแรกบนเว็บไซต์ของมหาวิทยาลัยแอดิเลด (เนื้อหาของงานนี้อยู่ในโดเมนสาธารณะในออสเตรเลีย)

ข้อความทั้งหมดจะต้องวิเคราะห์เป็นหนึ่งสตริงเป็นประโยคยาวหนึ่งคำเป็นเวกเตอร์ที่มีความยาวหนึ่งคำ (แล้วแต่ว่าภาษาใดจะสะดวกกว่าสำหรับคุณ) การแยกบรรทัดย่อยและแยกเป็นคำที่เว้นวรรคแยกออกเป็นคำที่ช่องว่าง(ฉันไม่ได้ให้ไฟล์ย่อหน้าเดียวเพราะอาจถูกขมวดคิ้วโดยเครื่องมือ github)

ฉันจะอ่านข้อความทั้งหมดเป็นหนึ่งสาย / ประโยคได้อย่างไร ตัวอย่างใน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 ที่สอดคล้องกันโดยใช้โซ่ความน่าจะเป็นเพื่อคาดการณ์คำถัดไปXตามข้อความการฝึกอบรมนี้ที่ถือว่าเป็นประโยคยาวหนึ่งประโยค

ถ้าXเป็นคำ T9 แรกของข้อความและมีการเดาหลายคำให้เลือกหนึ่งคำโดยสุ่มหรือเลือกเพียงคำเดียวที่เป็นไปได้

สำหรับ T9 คำต่อมาทั้งหมดX (i)นำหน้าด้วยคำที่ถอดรหัสแล้วw (i-1) :

  1. หาก T9-word Xสามารถแปลงเป็นคำปกติxในวิธีที่ไม่ซ้ำกันให้ทำ
  2. ถ้ามีตัวเลือกหลายแปลงสามารถใช้ได้สำหรับXพูดx1, x2, ...มองขึ้นคำเดาก่อนW
    • หากwไม่เคยตามมาด้วยสิ่งใดที่จับคู่กับXในงานดั้งเดิมของ Proust ให้เลือกx1, x2, ...ที่เป็นไปได้แบบสุ่ม
    • ถ้ากว้าง xเสมอสอดคล้องกับW x1ในต้นฉบับและไม่มีพร้อมกันXi ‘s ที่สามารถแมปลงในXเลือกx1
    • ถ้ากว้าง xสามารถแปลงW x1 , W x 2 ... ที่สามารถพบได้ในคลังแล้วนับเป็นไปได้ทั้งหมดXi ‘s ติดตามว่าWและแผนที่เพื่อXในคลังและรับXiกับความน่าจะเป็นจิน / (x1 + x2 + ... )

ตัวอย่าง 2a หากข้อความอยู่76630489ที่489ใดguyหรือivy(เกิดขึ้นในคลังอย่างน้อยหนึ่งครั้ง) 7663สามารถถอดรหัสเป็นsome(คำแรกที่เป็นไปได้มาก) หากsomeไม่เคยตามมาด้วยสิ่งใดก็ตามที่แมปไป489ในคลังข้อมูลให้เลือกguyหรือivyสุ่มด้วยความน่าจะเป็น 0.5

ตัวอย่าง 2b หากข้อความ766302277437ที่2277437อาจเป็นbarrierหรือcarrier, สามารถถอดรหัสเป็น7663 someหาก Proust ใช้เสมอsome carrierและไม่เคยแล้วเลือกsome barriersome carrier

ตัวอย่าง 2c 536307663สมมติว่าคุณต้องการที่จะถอดรหัสลำดับ เป็นที่คาดการณ์ว่าเป็น5363 อาจจะมีของเหล่านี้: , และ คุณนับจำนวนคำที่ตามมาในคลังตัวอย่าง สมมติว่าคุณได้รับสิ่งนี้ (เพื่ออธิบาย):lend7663pondroofsomelend

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

ดังนั้นหาก7663จะนำหน้าด้วยlendมี7/(7+2+1)=70%ความน่าจะเป็นที่7663หมายถึงsome20% pondและ roof10% อัลกอริทึมของคุณควรผลิตlend someใน 70% รายและlend pond20% ราย

คุณอาจสันนิษฐานได้อย่างปลอดภัยว่าตัวแทนใช้ตัวอักษร az และช่องว่างเท่านั้น (ไม่มีเครื่องหมายวรรคตอนไม่มีการครอบครอง'sและไม่มีตัวเลข)

คุณอาจคิดว่าตัวแทนของ M1S ไม่เคยใช้คำใด ๆ ที่อยู่นอกขอบเขตของ "ความทรงจำในอดีต" (ซึ่งเป็นคำศัพท์ที่น่าสนใจจำนวน 29,237 คำ!)

Fuctionality 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)

คุณต้องระวังให้มากที่สุดดังนั้นรหัสที่สั้นที่สุดจะเป็นผู้ชนะ!

ป.ล.ประโยชน์ที่ชัดเจนของอัลกอริธึมความน่าจะเป็นนี้คือความเป็นไปได้ที่คุณจะได้รับสตริงต้นฉบับจริงสำหรับสตริงถอดรหัสที่ไม่ชัดเจนซึ่งมีแนวโน้มที่จะเป็นหนึ่ง - เพียงแค่รอ ...

PPS ดูเพิ่มเติมทำนายโดยบางส่วนจับคู่


ข้อสังเกตของ Peter Taylorจากกล่องทรายถูกนำมาพิจารณา น่าเสียดายที่มีผู้ตอบไม่กี่คนในระหว่างสัปดาห์ที่โพสต์ที่นี่แม้จะมีการอัปเดตหลายครั้งดังนั้นจึงยินดีรับข้อเสนอแนะ! BTW นี่คือความท้าทายครั้งแรกของฉัน!
Andreï Kostyrka

ฉันสงสัยว่าเหตุผลใหญ่ที่คุณไม่ได้รับการตอบสนองมากเป็นเพราะความรู้ขั้นสูงที่จำเป็นในการเข้าใจปัญหานี้ หากคุณกำลังต้องการความท้าทายนี้จะอุทธรณ์ไปยังฝูงชนขนาดใหญ่ผมอยากแนะนำให้รวมทั้งบางตัวอย่างก่อนหน้านี้ที่แสดงลูกโซ่มาร์คอฟที่ทำงาน :)
นาธานเมอร์ริ

@NathanMerrill ตกลงฉันได้เพิ่ม 3 ลิงก์ไปยังตัวอย่างความท้าทาย อย่างไรก็ตามผู้ใช้ไม่จำเป็นต้องรู้ว่ามาร์คอฟเชนเลยเพราะงานนั้นได้อธิบายไว้ในเนื้อหาคำถามว่าเป็นอัลกอริธึมที่เป็นไปได้มากที่สุด: ถ้า X ให้ทำ Y ด้วยความน่าจะเป็นที่ได้จากการคำนวณ Z ในตัวอย่างการเรียนรู้นี้ ผมพยายามที่จะทำให้มันเป็นแบบพอเพียงที่จะได้รับ ...
อังเดร 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-ifies ข้อความใด ๆ :

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-ify Proust:

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และถอดรหัสคำทีละ:

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" 

ตัวอย่างที่สอง:

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"

โปรดอย่าแสดงความคิดเห็นว่าสิ่งนี้สามารถเล่นกอล์ฟได้ ดูเหมือนว่าบางคนมีความสนใจในความท้าทายนี้เนื่องจากความฟุ้งซ่านอย่างรุนแรงของฉันดังนั้นฉันโพสต์คำตอบนี้เพื่อแสดงว่าโปรแกรมที่เป็นไปได้อาจมีลักษณะอย่างไร คุณไม่จำเป็นต้อง upvote / downvote คำตอบนี้


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.