Morse Decode Golf

24

Я встревожился растущей ненавистью к пробелам, и этот ответ вдохновил меня на то, чтобы код Морзе был защищен от этого коварного удаления пробелов.

Итак, ваша задача будет состоять в том, чтобы создать программу, которая сможет успешно переводить азбуку Морзе со всеми удаленными пробелами.

азбука Морзе

Правила:

  1. На входе будет строка, состоящая только из тире и точек (ASCII 2D и 2E). Вывод не определен для ввода, содержащего любые другие символы. Не стесняйтесь использовать любой метод, удобный для вашего языка, для получения ввода (стандартный ввод, текстовый файл, приглашение пользователя и т. Д.). Можно предположить, что ввод азбуки Морзе состоит только из букв AZ, и соответствующие цифры или знаки препинания не требуются.

  2. Вывод должен включать только слова, содержащиеся в этом файле словаря (опять же, не стесняйтесь использовать любой удобный метод для доступа к файлу словаря). Все действительные декодирования должны быть выведены на стандартный вывод, и должны быть использованы все точки и тире на входе. Каждое совпадающее слово в выходных данных должно быть разделено пробелом, а каждое возможное декодирование должно быть отделено новой строкой. Вы можете использовать верхний регистр, нижний регистр или смешанный регистр как удобно.

  3. Все ограничения на стандартные лазейки применяются с одним исключением, как отмечено выше, вы можете получить доступ к файлу словаря, на который есть ссылка в требовании 2, через интернет-соединение, если вы действительно этого хотите. Сокращение URL приемлемо, я считаю, что goo.gl/46I35Z , вероятно, самый короткий.

  4. Это код гольф, самый короткий код выигрывает.

Примечание. При публикации файла словаря на Pastebin все окончания строк изменились на последовательности в стиле 0A 0E в стиле Windows. Ваша программа может предполагать окончания строки только с 0A, только 0E или 0A 0E.

Тестовые случаи:

Входные данные:

......-...-.. ---. ----- ...-..- ..

Вывод должен содержать:

Привет, мир

Входные данные:

...,. ----- .... ----- ... - .. - ... --- .. - ...-.... ... - ...-.. ---- ... -. ----....-..

Вывод должен содержать:

программирование головоломок и код гольфа

Входные данные:

-..... - ...-...-.. -.. ...., .... --- --- ---- ..-.- --.. --- -. .... --- ...-...-......-... --- ... --- ..-- ---.

Вывод должен содержать:

Быстрая коричневая лиса прыгает через ленивую собаку

Коминтерн
источник
3
Как вы можете сказать между AN (.- -.)и EG (. --.)?
Seequ
2
@Sieg - Выходные данные должны включать оба допустимых декодирования.
Коминтерн
1
@ Денис - Аааа ... я готов поспорить, что это сделал либо Пастебин, либо мой браузер. Мой исходный файл не имеет их. Вы можете изменить разделитель строк на соответствующий системе, без других изменений. Я отредактирую вопрос, когда меня не будет на моем телефоне.
Коминтерн
2
@ Фалько, это правильное поведение. Обратите внимание, что проблема говорит о том, что ваш вывод должен включать «привет мир», а не то, что он ограничен этим. Он должен распечатать все действительные расшифровки.
Хоббс
2
(почти поэтично, правда)
Хоббс

Ответы:

5

Рубин, 210

(1..(g=gets).size).map{|x|puts IO.read(?d).split.repeated_permutation(x).select{|p|p.join.gsub(/./,Hash[(?a..?z).zip"(;=/%513':07*)29@-+&,4.<>?".bytes.map{|b|('%b'%(b-35))[1,7].tr'01','.-'}])==g}.map{|r|r*' '}}

Если существует такая практика, как «чрезмерная игра в гольф», я подозреваю, что принял участие в этот раз. Это решение генерирует массив массивов повторяющихся перестановок всех слов словаря , от длины 1 до длины ввода. Учитывая, что «a» является самым коротким словом в файле словаря, а его код состоит из двух символов, было бы достаточно генерировать перестановки длиной до половины размера ввода, но добавление /2равнозначно многословности в этой области, поэтому я воздержался.

После того, как массив перестановок был сгенерирован ( примечание : он имеет длину 45404 104 в случае входных данных панграмматического примера), каждый массив перестановок объединяется, и его буквенные символы заменяются их эквивалентами кода Морзе через довольно удобный (Regexp, Hash)вариант #gsubСпособ; мы нашли правильное декодирование, если эта строка равна входу.

Словарь читается (несколько раз) из файла с именем "d", и ввод не должен содержать символ новой строки.

Пример выполнения (со словарем, который даст программе шанс на сражение до конца перед тепловой смертью вселенной):

$ cat d
puzzles
and
code
dummy
golf
programming
$ echo -n .--..-.-----..-..-----..-.--..--...---..--...-.......--.-..-.-.----...--.---.-....-. | ruby morse.rb
programming puzzles and code golf
^C
Три, Если Виски
источник
5

Haskell, 296 символов

  • Файл словаря: должен быть текстовым файлом с именем "d"
  • Ввод: stdin, может иметь завершающий символ новой строки, но не иметь внутреннего пробела
main=do f<-readFile"d";getLine>>=mapM(putStrLn.unwords).(words f&)
i!""=[i]
(i:j)!(p:q)|i==p=j!q
_!_=[]
_&""=[[]]
d&i=do
w<-d
j<-i!(w>>=((replicate 97"X"++words".- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--. --.- .-. ... - ..- ...- .-- -..- -.-- --..")!!).fromEnum)
n<-d&j
[w:n]

Объяснение элементов:

  • mainчитает словарь, читает stdin, выполняет &и форматирует выходные данные &с соответствующим пробелом.
  • (replicate 97"X"++words".- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--. --.- .-. ... - ..- ...- .-- -..- -.-- --..")!!)(выражение внутри определения &) представляет собой список, индексы которого представляют собой коды символов (97 - это код 'a'), а значения - последовательности Морзе.
  • !(функция, называемая инфиксным оператором) сопоставляет строку с префиксом; если префикс присутствует, он возвращает остаток в одноэлементном списке (успех в монаде списка), в противном случае пустой список (ошибка в монаде списка)
  • &использует монаду списка для «недетерминированного» исполнения; Это

    1. выбирает запись d(словарное слово),
    2. используется !для сопоставления формы Морзе этого слова ( w>>=((…)!!).fromEnumчто эквивалентно concatMap (((…)!!) . fromEnum) w) с входной строкой i,
    3. вызывает себя ( d&j), чтобы соответствовать остальной части строки, и
    4. возвращает возможный результат в виде списка слов w:nв монаде списка [w:n](которая является более коротким, конкретным эквивалентом return (w:n)).

    Обратите внимание, что каждая строка после строки 6 является частью doвыражения, начинающегося со строки 6; это занимает ровно столько же символов, что и точки с запятой в одной строке, но более читабельно, хотя вы можете сделать это только один раз в программе.

Эта программа очень медленная. Это можно сделать быстрее (и немного дольше) легко, сохраняя сокращенные слова рядом с оригиналами в списке, а не пересчитывая их при каждом совпадении с образцом. Следующее, что нужно сделать, - это сохранить слова в бинарном дереве, снабженном символами Морзе (2-кратная последовательность ), чтобы избежать попыток ненужных ветвей.

Это можно сделать немного короче, если файл словаря не содержит неиспользуемые символы, такие как «-», что позволяет удалить replicate 97"X"++в пользу выполнения .(-97+)перед !!.

Кевин Рид
источник
Черт возьми, это умно. +1 тебе.
Александр Краггс
1
Знаете ли вы, что (+(-97))можно переписать как (-97+)?
гордый haskeller
Вы должны удалить третье определение h и вместо этого добавить |0<1=[]ко второму определению
гордый haskeller
2
Используйте interactи выиграйте 12 символов. interact$unlines.map unwords.(words f&)
gxtaillon
1
Вы должны быть в состоянии заменить concatMapна>>=
гордый haskeller
3

Питон - 363 345

Код:

D,P='-.';U,N='-.-','.-.'
def s(b,i):
 if i=='':print b
 for w in open('d').read().split():
  C=''.join([dict(zip('abcdefghijklmnopqrstuvwxyz-\'23',[P+D,D+3*P,U+P,'-..',P,D+N,'--.',4*P,2*P,P+3*D,U,N+P,2*D,D+P,D*3,'.--.',D+U,N,P*3,D,'..-',3*P+D,'.--','-..-',U+D,'--..']+['']*4))[c]for c in w]);L=len(C)
  if i[:L]==C:s(b+' '+w,i[L:])
s('',input())

Объяснение:

Словарь должен храниться в виде простого текстового файла с именем "d".

D, P, UИ Nтолько некоторые вспомогательные переменные для более короткого определения таблицы Морзе поиска.

s(i)является рекурсивной функцией, которая печатает ранее переведенную часть сообщения pи каждый действительный перевод оставшейся части кода i: если iпусто, мы достигли конца кода, и bсодержит весь перевод, таким образом, мы просто printего. В противном случае мы проверяем каждое слово wв словаре d, переводим его в азбуку Морзе Cи, если оставшийся код iначинается с C, мы добавляем слово wв переведенное начало bи sрекурсивно вызываем функцию для остатка.

Примечание по эффективности:

Это довольно медленная, но играющая в гольф версия. Особенно загрузка словаря и построение таблицы поиска Морзе ( dict(zip(...))) в каждой итерации (чтобы избежать большего количества переменных) стоит дорого. И было бы более эффективно переводить все слова в файле словаря заранее, а не в каждой рекурсии по требованию. Эти идеи приводят к следующей версии, содержащей еще 40 символов, но значительно ускоренной:

d=open('d').read().split()
D,P='-.';U,N='-.-','.-.'
M=dict(zip('abcdefghijklmnopqrstuvwxyz-\'23',[P+D,D+3*P,U+P,'-..',P,D+N,'--.',4*P,2*P,P+3*D,U,N+P,2*D,D+P,D*3,'.--.',D+U,N,P*3,D,'..-',3*P+D,'.--','-..-',U+D,'--..']+['']*4))
T=[''.join([M[c]for c in w])for w in d]
def s(b,i):
 if i=='':print b
 for j,w in enumerate(d):
  C=T[j];L=len(C)
  if i[:L]==C:s(b+' '+w,i[L:])
s('',input())
Фалько
источник
Вы можете сохранить 2 символа, заменив .startswith(C)на [:len(C)]==C.
Грег Хьюгилл
Вау, спасибо! Это становится довольно странно, так как загрузка всего словаря в каждой рекурсии сохраняет символы - и снова замедляет алгоритм.
Фалько
@GregHewgill: Да, это то, что я сделал изначально. Я только отредактировал свой ответ, чтобы обратиться к обеим версиям.
Фалько
1
Вы можете получить более интересные результаты (более длинные слова) быстрее, сортируя словарь по убыванию длины слов. d=sorted(open('d').read().split(),key=len,reverse=1)Или сделайте это внешне, предварительно отсортировав ваш словарь таким образом.
Грег Хьюгилл
Черт возьми, если вы можете переформатировать файл словаря, отформатируйте его как предварительно рассчитанный словарь Python и M=eval(open('d').read()):)
Грег Хьюгилл
3

Perl (5.10+), 293 символа

Файл словаря должен быть сохранен как «d» (и должен быть в формате unix, если вы не хотите CR между словами), ввод Морзе на stdin, без завершающей строки (используйте echo -n).

open D,d;chomp(@w=<D>);@m{a..z}=map{substr(sprintf("%b",-61+ord),1)=~y/01/.-/r}
'BUWI?OKMATJQDCLSZGE@FNHVXY'=~/./g;%w=map{$_,qr#^\Q@{[s/./$m{$&}/reg]}#}@w;
@a=[$i=<>];while(@a){say join$",@{$$_[1]}for grep!$$_[0],@a;
@a=map{$x=$_;map{$$x[0]=~$w{$_}?[substr($$x[0],$+[0]),[@{$$x[1]},$_]]:()}@w}@a}

(переносы строк только для форматирования).

Ungolfed код:

# Read the word list
open my $dictionary, '<', 'd';
chomp(my @words = <$dictionary>);

# Define morse characters
my %morse;
@morse{'a' .. 'z'} = map {
  $n = ord($_) - 61;
  $bits = sprintf "%b", $n;
  $bits =~ tr/01/.-/;
  substr $bits, 1;
} split //, 'BUWI?OKMATJQDCLSZGE@FNHVXY';

# Make a hash of words to regexes that match their morse representation
my %morse_words = map {
  my $morse_word = s/./$morse{$_}/reg;
  ($_ => qr/^\Q$morse_word/)
} @words;

# Read the input
my $input = <>;

# Initialize the state
my @candidates = ({ remaining => $input, words => [] });

while (@candidates) {
  # Print matches
  for my $solution (grep { $_->{remaining} eq '' } @candidates) {
    say join " ", @{ $solution->{words} }; 
  } 
  # Generate new solutions
  @candidates = map {
    my $candidate = $_;
    map {
      $candidate->{remaining} =~ $morse_words{$_}
        ? {
          remaining => substr( $candidate->{remaining}, $+[0] ),
          words => [ @{ $candidate->{words} }, $_ ],
        }
        : ()
    } @words
  } @candidates;
}

Модус Операнди:

Символы азбуки Морзе сохраняются путем изменения "." и «-» в двоичные цифры 0 и 1, добавляя «1» (так что начальные точки не сожрались), преобразовывая двоичное число в десятичное, а затем кодируя символ со значением 61 выше (что дает мне все печатные символы и ничего, что не требует обратной косой черты).

Я подумал об этом как о проблеме разделения и создал решение, основанное на этом. Для каждого слова в словаре он создает объект регулярного выражения, который будет сопоставлять (и захватывать) представление без пробела этого слова в начале строки. Затем он начинает поиск в ширину, создавая состояние, которое не соответствует ни одному слову и имеет весь ввод в качестве «оставшегося ввода». Затем он расширяет каждое состояние, ища слова, которые совпадают в начале оставшегося ввода, и создает новые состояния, которые добавляют слово к подобранным словам и удаляют морзе из оставшегося ввода. Состояния, которые не имеют оставшихся входных данных, являются успешными и имеют напечатанный список слов. Состояния, которые не могут соответствовать ни одному слову (включая успешные), не генерируют дочерние состояния.

Обратите внимание, что в негольфированной версии состояния являются хэшами для удобства чтения; в версии для гольфа они являются массивами (для более короткого кода и меньшего потребления памяти); Слот [0]- это оставшиеся входные данные, а слот [1]- это совпадающие слова.

Комментарий

Это безбожно медленно. Мне интересно, есть ли решение, которого нет. Я попытался построить один с использованием Marpa (парсер Earley с возможностью давать несколько разборов для одной входной строки), но мне не хватило памяти только для построения грамматики. Возможно, если бы я использовал низкоуровневый API вместо ввода BNF ...

Hobbs
источник
Если я добавлю то же требование, что и Кевин Рейд (без ввода новой строки), я могу сохранить 7 символов, удалив chomp(). Нужно ли мне?
Хоббс
«Не стесняйтесь использовать любой удобный метод».
Коминтерн
Бритье 2 байта с ordвместо ord$_. Бритье, говоря 1 байт join$"вместоjoin" "
Заид
2

Хаскелл - 418

Эта проблема декорирования может быть эффективно решена путем динамического программирования. Я знаю, что это Codegolf, но я люблю быстрый код.

Скажем, у нас есть входная строка s, затем мы строим массив dp, dp[i]это список всех допустимых результатов декодирования подстроки s[:i]. Для каждого слова wв словаре сначала мы его кодируем mw, а затем можем вычислить часть dp[i]из dp[i - length(mw)]if s[i - length(mw):i] == mw. Временная сложность строительства dpсоставляет O({count of words} {length of s} {max word length}). Наконец, dp[length(s)]последний элемент - это то, что нам нужно.

На самом деле нам не нужно хранить полное декодирование как элемент каждого dp[i]. Нам нужно последнее расшифрованное слово. Это делает реализацию намного быстрее. Чтобы завершить дело «Привет, мир» на моем ноутбуке i3, понадобилось менее 2 секунд. Для других случаев, размещенных в вопросе, программа не завершится практически, так как их слишком много для вывода.

Используя технику динамического программирования, мы можем вычислить количество допустимых декодирований . Вы можете найти код здесь . Полученные результаты:

input: ......-...-..---.-----.-..-..-..
count: 403856

input: .--..-.-----..-..-----..-.--..--...---..--...-.......--.-..-.-.----...--.---.-....-.
count: 2889424682038128

input: -.....--.-..-..-.-.-.--....-.---.---...-.----..-.---..---.--....---...-..-.-......-...---..-.---..-----.
count: 4986181473975221635

Ungolfed

import Control.Monad

morseTable :: [(Char, String)]
morseTable = zip ['a'..'z'] $ words ".- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--. --.- .-. ... - ..- ...- .-- -..- -.-- --.."

wordToMorse :: String -> Maybe String
wordToMorse xs = return . concat =<< mapM (`lookup` morseTable) xs

slice :: (Int, Int) -> [a] -> [a]
slice (start, end) = take (end - start) . drop start

decode :: String -> String -> IO ()
decode dict s = trace (length s) [] where
  dict' = [(w, maybe "x" id . wordToMorse $ w) | w <- lines dict]
  dp = flip map [0..length s] $ \i -> [(j, w) |
        (w, mw) <- dict', let j = i - length mw, j >= 0 && mw == slice (j, i) s]

  trace :: Int -> [String] -> IO ()
  trace 0 prefix = putStrLn . unwords $ prefix
  trace i prefix = sequence_ [trace j (w:prefix) | (j, w) <- dp !! i]

main :: IO ()
main = do
  ws <- readFile "wordlist.txt"
  decode ws =<< getLine

Golfed

import Control.Monad
l=length
t=zip['a'..]$words".- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--. --.- .-. ... - ..- ...- .-- -..- -.-- --.."
h s=return.concat=<<mapM(`lookup`t)s
f d s=g(l s)[]where g 0 p=putStrLn.unwords$p;g i p=sequence_[g j(w:p)|(j,w)<-map(\i->[(j,w)|(w,m)<-[(w,maybe"x"id.h$w)|w<-lines d],let j=i-l m,j>=0&&m==(take(i-j).drop j$s)])[0..l s]!!i]
main=do d<-readFile"d";f d=<<getLine
луч
источник
Рад видеть достаточно эффективное решение. Теперь я просто должен это понять :)
Хоббс
2

PHP, 234 226 байт

function f($s,$r=""){$s?:print"$r
";foreach(file(d)as$w){for($i=+$m="";$p=@strpos(__etianmsurwdkgohvf_l_pjbxcyzq,$w[$i++]);)$m.=strtr(substr(decbin($p),1),10,"-.");0!==strpos($s,$m)?:g(substr($s,strlen($m)),$r.trim($w)." ");}}

рекурсивная функция, берет словарь из файла с именем d.
Сбой для каждого слова в словаре, содержащего не букву.

Вы можете использовать любое имя файла, если вы define ("d","<filename>");перед вызовом функции.

Добавьте 2 или 3 байта для более быстрого выполнения:
Удалить $s?:print"$r\n";, вставить $s!=$m?до 0!==и :print$r.$wперед ;}}.

сломать

function f($s,$r="")
{
    $s?:print"$r\n";            // if input is empty, print result
    foreach(file(d)as$w)        // loop through words
    {
        // translate to morse:
        for($i=+$m="";              // init morse to empty string, $i to 0
                                        // loop while character is in the string
            $p=@strpos(__etianmsurwdkgohvf_l_pjbxcyzq,$w[$i++])
        ;)
            $m.=                        // 4. append to word morse code
                strtr(
                    substr(
                        decbin($p)      // 1: convert position to base 2
                    ,1)                 // 2: substr: remove leading `1`
                ,10,"-.")               // 3. strtr: dot for 0, dash for 1
            ;
        0!==strpos($s,$m)           // if $s starts with $m
            ?:f(                        // recurse
                substr($s,strlen($m)),  // with rest of $s as input
                $r.trim($w)." "         // and $r + word + space as result
            )
        ;
    }
}
Titus
источник
1

Groovy 377 337

r=[:];t={x='',p=''->r[s[0]]=p+x;s=s.substring(1);if(p.size()<3){t('.',p+x);t('-',p+x)}};s='-eishvuf-arl-wpjtndbxkcymgzqo--';t()
m=('d'as File).readLines().groupBy{it.collect{r.get(it,0)}.join()}
f={it,h=[]->it.size().times{i->k=it[0..i]
if(k in m){m[k].each{j->f(it.substring(i+1),h+[j])}}}
if(it.empty){println h.join(' ')}}
f(args[0])

Заметки

Диктовкой должен быть файл с именем d. Строка Морзе передается из командной строки. например:

% groovy morse.groovy ......-...-..---.-----.-..-..-.. | grep 'hello world'
hello world

Для "сжатия кода Морзе" я использую двоичное дерево

cfrick
источник