Марсель Пруст і Марков розшифрували тексти T9 ​​служби безпеки


11

Ніби цей виклик міг би бути більше піфонескою за духом ... Ніяких попередніх знань про ланцюги Маркова або методи шифрування не потрібно.

Ви шпигун, якому потрібно отримати важливу інформацію від британської служби безпеки M1S. Агенти M1S прекрасно знають, що їхні сигнали Wi-Fi можуть бути перехоплені, їх захищені вразливості для Android / iOS тощо. Тому всі вони використовують Nokia 3310 для передачі текстової інформації, яка набирається за допомогою автоматичного заповнення T9 .

Ви раніше зламали телефони для доставки в розвідувальну агенцію і встановили кейлоггери під їхніми славними пластиковими клавіатурами, тож тепер ви отримуєте послідовності номерів, що відповідають літерам, які вони набрали, так що « орел покинув гніздо оповіщення агентів »

84303245304270533808430637802537808430243687

Але зачекайте! Деякі послідовності T9 неоднозначні ("6263" може бути "ім'я", "грива" або "гобой"; чим більше незрозумілим, тим підозрілішим воно стає!), І що робити? Ви знаєте, що єдиним вступним іспитом, який M1S використовує, є підведення підсумків шедевра Марселя Пруста «Пам'ять речей минулого» за 15 секунд, тож ви хочете вибрати слово, яке буде наступним після попереднього, відповідно до його частотного розподілу в цілому шеф-кухаря. œuvre Пруста!

Чи можете ви зламати код і отримати те, що може бути оригінальним повідомленням?

Принцип 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, 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-слово X можна перетворити в звичайне слово x одним унікальним способом, зробіть це.
  2. Якщо для X , скажімо x1, x2, ... , доступно кілька варіантів перетворення , знайдіть попереднє здогадане слово w .
    • Якщо за початковим твором Пруста w ніколи не дотримується нічого, що позначає X , виберіть будь-який із можливих x1, x2, ... навмання.
    • Якщо w X завжди відповідає w x1 в оригіналі і немає одночасних xi , які можна було б відобразити в X , виберіть x1 .
    • Якщо w X можна перетворити на w x1 , w x2 , ..., які можуть бути знайдені в корпусі, то порахуйте всі можливі xi , які слідують w, і переведіть на X у корпусі, і виберіть xi з вірогідністю xi / (x1 + x2 + ...) .

Приклад 2a. Якщо повідомлення є 76630489, де це 489може бути guyабо ivy(вони трапляються в корпусі хоча б один раз), 7663можна розшифрувати як some(дуже ймовірне перше слово). Якщо someніколи не супроводжується чимось, що відображається 489в корпусі, виберіть guyабо ivyнавмання з імовірністю 0,5.

Приклад 2b. Якщо повідомлення є 766302277437, де це 2277437може бути barrierабо carrier, 7663можна розшифрувати як some. Якщо Пруст завжди використовувався some carrierі ніколи some barrier, тоді вибирайте some carrier.

Приклад 2c. Припустимо, ви хочете розшифрувати послідовність 536307663. 5363було прогнозовано як lend. 7663може бути будь-який з них: pond, roofі some. Ви підраховуєте випадки слова, наступного lendу зразку корпусу. Припустимо, у вас виходить щось подібне (просто для ілюстрації):

        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 pondу 20% випадків тощо.

Ви можете з упевненістю припустити, що агенти використовують лише букви та пробіли (без розділових знаків, ніяких присвійних '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, завжди доступний.
  • Ваш алгоритм повинен мати можливість виробляти різні результати з відповідними ймовірностями, якщо відповідно до корпусу є більше ніж один варіант дешифрування (див. Приклад 2в).

Вам потрібно залишатися максимально стриманим, щоб виграв найкоротший код!

PS Очевидною перевагою цього ймовірнісного алгоритму є той факт, що ймовірність, що ви отримаєте справжню оригінальну рядок для неоднозначної розшифрованої рядки, має тенденцію до однієї - просто зачекайте ...

PPS Див. Також Прогноз шляхом часткового узгодження .


Зауваження Пітера Тейлора з пісочниці були враховані. На жаль, дуже мало людей відповіли протягом тижня, коли він був розміщений там, незважаючи на багаторазові оновлення, тому будь-які пропозиції вітаються! До речі, це мій перший виклик!
Андрей Костирка

Я підозрюю, що велика причина, по якій ви не отримали багато відповідей, пов’язана з передовими знаннями, необхідними для розуміння цієї проблеми. Якщо ви хочете, щоб цей виклик сподобався більшій натовпі, я рекомендую включити кілька попередніх прикладів, які показують ланцюжок марків на роботі :)
Натан Меррілл

@NathanMerrill Гаразд, я додав 3 посилання на вибіркові завдання. Однак користувачеві зовсім не потрібно знати ланцюги Маркова, оскільки завдання описується в тілі запитань як можна алгоритмічніше: якщо X, зробіть Y з ймовірностями, отриманими обчисленням Z у цій вибірці навчання. Я намагався зробити це настільки самодостатнім, як це стає ...
Андрей Костирка,

О Я розумію. Якби ти не пояснив це, я би проголосував за його закриття. Просто схоже, що їй потрібні передові знання :)
Натан Меррілл

1
Мені подобається цей виклик, але я ще не встиг сісти і створити / гольф рішення. Сподіваємось, що це станеться незабаром.
Мего

Відповіді:


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-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
}

А тепер я пропоную функцію, яка приймає будь-який вхідний рядок чисел, preps і розшифровує слова по одному:

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"

Будь ласка, не коментуйте, що це може бути гольф. Здається, що мало кого цікавить цей виклик через мою страшну багатослівність, тому я опублікував цю відповідь, щоб показати, як може виглядати можлива програма. Вам не потрібно підтверджувати / знижувати цю відповідь.


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=' ')
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.