Morse Decode Golf


24

Fiquei alarmado com o crescente ódio aos espaços e essa resposta me inspirou a garantir que o código Morse esteja a salvo dessa remoção insidiosa do espaço em branco.

Portanto, sua tarefa será criar um programa que possa traduzir com êxito o código Morse com todos os espaços removidos.

Código Morse

Regras:

  1. A entrada será uma sequência que consiste apenas de traços e pontos (ASCII 2D e 2E). A saída é indefinida para a entrada que contém outros caracteres. Sinta-se à vontade para usar qualquer método conveniente ao seu idioma de escolha para receber a entrada (stdin, arquivo de texto, usuário imediato, qualquer que seja). Você pode supor que a entrada do código Morse consiste apenas nas letras AZ e que números ou pontuação correspondentes não são necessários.

  2. A saída deve incluir apenas as palavras contidas nesse arquivo de dicionário (novamente, sinta-se à vontade para usar qualquer método conveniente para acessar o arquivo de dicionário). Todas as decodificações válidas devem ser enviadas para stdout e todos os pontos e traços na entrada devem ser usados. Cada palavra correspondente na saída deve ser separada por um espaço e cada possível decodificação deve ser separada por uma nova linha. Você pode usar letras maiúsculas, minúsculas ou letras maiúsculas como conveniente.

  3. Todas as restrições sobre brechas padrão se aplicam com uma exceção, conforme mencionado acima; você pode acessar o arquivo de dicionário mencionado no requisito 2 por meio de uma conexão à Internet, se realmente desejar. O encurtamento de URL é aceitável, acredito que o goo.gl/46I35Z é provavelmente o mais curto.

  4. Este é o código de golfe, o código mais curto vence.

Nota: A publicação do arquivo de dicionário no Pastebin alterou todas as terminações de linha para sequências 0A 0E do estilo Windows. Seu programa pode assumir terminações de linha apenas com 0A, apenas 0E ou 0A 0E.

Casos de teste:

Entrada:

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

A saída deve conter:

Olá Mundo

Entrada:

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

A saída deve conter:

quebra-cabeças de programação e código de golfe

Entrada:

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

A saída deve conter:

a rápida raposa marrom pula sobre o cachorro preguiçoso


3
Como você pode dizer entre AN (.- -.)e EG (. --.)?
seequ

2
@ Sii - A saída precisaria incluir ambas as decodificações válidas.
Comintern

11
@ Dennis - Ahhh ... aposto que Pastebin ou meu navegador fizeram isso. Meu arquivo de origem não os possui. Você pode alterar o delimitador de linha para um sistema apropriado, sem outras alterações. Vou editar a pergunta quando não estiver no telefone.
Comintern

2
@ Falko, esse é o comportamento correto. Observe que o problema diz que sua saída deve incluir "olá mundo", não que seja limitada a isso. Ele deve imprimir todas as decodificações válidas.
Hbbs

2
(quase poético, realmente)
hobbs

Respostas:


5

Ruby, 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*' '}}

Se existe uma prática como "excesso de golfe", suspeito que tenha participado desta vez. Esta solução gera uma matriz de matrizes de permutações repetidas de todas as palavras do dicionário , do comprimento 1 até o comprimento da entrada. Dado que "a" é a palavra mais curta no arquivo de dicionário e seu código tem dois caracteres, seria suficiente gerar permutações de comprimento até a metade do tamanho da entrada, mas adicionar /2é equivalente à verbosidade neste domínio, então eu me abstive.

Depois que a matriz de permutação é gerada ( NB : é de comprimento 45404 104 no caso da entrada de exemplo pangramática), cada matriz de permutação é concatenada e seus caracteres alfabéticos são substituídos por seus equivalentes de código Morse por meio da (Regexp, Hash)variante bastante conveniente do #gsubmétodo; encontramos uma decodificação válida se essa sequência for igual à entrada.

O dicionário é lido (várias vezes) em um arquivo chamado "d" e a entrada não deve conter uma nova linha.

Exemplo de execução (com um dicionário que dará ao programa a chance de terminar antes da morte pelo calor do universo):

$ cat d
puzzles
and
code
dummy
golf
programming
$ echo -n .--..-.-----..-..-----..-.--..--...---..--...-.......--.-..-.-.----...--.---.-....-. | ruby morse.rb
programming puzzles and code golf
^C

5

Haskell, 296 caracteres

  • Arquivo de dicionário: deve ser um arquivo de texto chamado "d"
  • Entrada: stdin, pode ter uma nova linha à direita, mas não há espaço em branco interno
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]

Explicação dos elementos:

  • mainlê o dicionário, lê stdin, executa &e formata a saída &com espaço em branco apropriado.
  • (replicate 97"X"++words".- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--. --.- .-. ... - ..- ...- .-- -..- -.-- --..")!!)(uma expressão dentro da definição de &) é uma lista cujos índices são códigos de caracteres (97 é o código de 'a') e valores são sequências Morse.
  • !(uma função nomeada como operador de infixo) corresponde a uma sequência contra um prefixo; se o prefixo estiver presente, ele retornará o restante em uma lista de um elemento (êxito na mônada da lista); caso contrário, na lista vazia (falha na mônada da lista)
  • &usa a mônada da lista para execução "não determinística"; isto

    1. escolhe uma entrada de d(uma palavra do dicionário),
    2. usa !para combinar a forma Morse dessa palavra ( w>>=((…)!!).fromEnumque é equivalente a concatMap (((…)!!) . fromEnum) w) à string de entrada i,
    3. chama a si próprio ( d&j) para corresponder ao restante da string e
    4. retorna o resultado possível como uma lista de palavras w:n, na mônada da lista [w:n](que é o equivalente mais curto e concreto a return (w:n)).

    Observe que todas as linhas após a linha 6 fazem parte da doexpressão iniciada na linha 6; isso leva exatamente o mesmo número de caracteres que o ponto e vírgula em uma única linha, mas é mais legível, embora você possa fazer isso apenas uma vez em um programa.

Este programa é extremamente lento. Isso pode ser mais rápido (e um pouco mais longo) facilmente, armazenando as palavras modificadas ao lado dos originais em uma lista, em vez de recalculá-las em cada correspondência de padrão. A próxima coisa a fazer seria para armazenar as palavras em uma árvore binária introduzidos por símbolos Morse (a 2 ary Trie ) de modo a evitar a tentar ramos desnecessários.

Pode ser feito um pouco mais curto, se o arquivo de dicionário não contém símbolos não utilizados tais como "-", permitindo a remoção replicate 97"X"++da favor de fazer .(-97+)antes da !!.


Maldito filho, isso é inteligente. +1 para você.
Alexander Craggs

11
Você sabia que isso (+(-97))pode ser reescrito (-97+)?
haskeller orgulhoso

Você deve remover a terceira definição de h e em vez adicionar |0<1=[]à segunda definição
haskeller orgulhoso

2
Use interacte ganhe 12 caracteres. interact$unlines.map unwords.(words f&)
Gxtaillon

11
Você deve ser capaz de substituir concatMappor>>=
proud haskeller

3

Python - 363 345

Código:

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

Explicação:

O dicionário deve ser armazenado como um arquivo de texto sem formatação chamado "d".

D, P, UEN são apenas algumas variáveis auxiliares para uma definição mais curta da tabela de pesquisa morse.

s(i)é uma função recursiva que imprime a parte da mensagem traduzida anteriormente pe cada tradução válida da parte restante do código i: Se iestiver vazia, chegamos ao final do código e bcontém a tradução inteira, portanto, simplesmente print. Caso contrário, verificamos cada palavra wno dicionário d, traduzimos para código morse Ce, se o código restante icomeçar C, adicionamos a palavra wao início traduzido be chamamos a funçãos recursivamente no restante.

Nota sobre eficiência:

Esta é uma versão bastante lenta, mas golfada. Especialmente carregar o dicionário e construir a tabela de pesquisa morse ( dict(zip(...))) em cada iteração (para evitar mais variáveis) custa muito. E seria mais eficiente traduzir todas as palavras no arquivo do dicionário uma vez com antecedência e não em cada recursão sob demanda. Essas idéias levam à seguinte versão, com mais 40 caracteres, mas aceleração significativa :

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

Você pode salvar 2 caracteres substituindo .startswith(C)por [:len(C)]==C.
Greg Hewgill

Uau, obrigada! Está ficando muito estranho, já que carregar o dicionário inteiro em cada recursão salva caracteres - e torna o algoritmo mais lento.
Falko

@ GregHewgill: Sim, foi o que eu fiz originalmente. Acabei de editar minha resposta para abordar as duas versões.
Falko

11
Você pode produzir resultados mais interessantes (palavras longas) mais rapidamente, classificando o dicionário por tamanho decrescente das palavras. d=sorted(open('d').read().split(),key=len,reverse=1)Ou faça isso externamente, classificando seu dicionário dessa maneira.
Greg Hewgill

Heck, se você pode reformatar o arquivo de dicionário, formatá-lo como um pré-calculado Python dicionário e M=eval(open('d').read()):)
Greg Hewgill

3

Perl (5.10+), 293 caracteres

O arquivo do dicionário deve ser salvo como "d" (e deve estar no formato unix, se você não quiser CRs entre as palavras), entrada morse no stdin, sem nova linha final (uso 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}

(quebras de linha apenas para formatação).

Código não destruído:

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

Modo de operação:

Os símbolos do código Morse são armazenados alterando "." e "-" nos dígitos binários 0 e 1, acrescentando um "1" (para que os pontos iniciais não sejam engolidos), convertendo o número binário em decimal e depois codificando o caractere com o valor 61 mais alto (o que me leva a tudo caracteres imprimíveis e nada que precise de barra invertida).

Pensei nisso como um tipo de problema de particionamento e construí uma solução com base nisso. Para cada palavra no dicionário, ele constrói um objeto regex que corresponderá (e capturará) a representação morse sem espaço dessa palavra no início de uma sequência. Em seguida, inicia uma pesquisa abrangente criando um estado que não corresponde a nenhuma palavra e que tem toda a entrada como "entrada restante". Em seguida, ele expande cada estado procurando por palavras que correspondam no início da entrada restante e criando novos estados que adicionam a palavra às palavras correspondentes e removem o morse da entrada restante. Os estados que não têm entrada restante são bem-sucedidos e têm sua lista de palavras impressa. Os estados que não podem corresponder a nenhuma palavra (incluindo as bem-sucedidas) não geram estados filhos.

Observe que na versão não-gasta os estados são hashes para facilitar a leitura; na versão golfed, são matrizes (para código mais curto e menor consumo de memória); slot [0]é a entrada restante e slot [1]são as palavras correspondentes.

Comentário

Isso é incrivelmente lento. Gostaria de saber se existe uma solução que não existe. Tentei criar um usando Marpa (um analisador Earley com a capacidade de fornecer várias análises para uma única sequência de entrada), mas fiquei sem memória apenas construindo a gramática. Talvez se eu usasse uma API de nível inferior em vez da entrada BNF ...


Se eu adicionar o mesmo requisito que Kevin Reid (nenhuma nova linha na entrada), posso salvar 7 caracteres removendo chomp(). Eu devo?
hobbs

"Sinta-se livre para usar qualquer método conveniente".
Comintern

Raspe 2 bytes com em ordvez de ord$_. Shave 1 byte dizendo join$"em vez dejoin" "
Zaid

2

Haskell - 418

Esse problema de remoção de estilo pode ser resolvido com eficiência por meio de programação dinâmica. Eu sei que isso é um codegolf, mas eu amo código rápido.

Digamos que temos a sequência de entrada e s, em seguida, construímos uma matriz dp, dp[i]é a lista de todos os resultados válidos de decodificação de substring s[:i]. Para cada palavra wno dicionário, primeiro codificando-a mw, então podemos calcular parte dp[i]do dp[i - length(mw)]se s[i - length(mw):i] == mw. A complexidade temporal do edifício dpé O({count of words} {length of s} {max word length}). Finalmente,dp[length(s)] o último elemento, é o que precisamos.

De fato, não precisamos armazenar toda a decodificação como o elemento de cada dp[i] . O que precisamos é a última palavra decodificada. Isso torna a implementação muito mais rápida. Custou menos de 2 segundos para finalizar o estojo "olá mundo" no meu laptop i3. Para outros casos postados na pergunta, o programa não será concluído na prática, pois há muitos para produzir.

Usando a técnica de programação dinâmica, podemos calcular o número de decodificações válidas . Você pode encontrar o código aqui . Resultados:

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

Golfe

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

Fico feliz em ver uma solução razoavelmente eficiente. Agora eu só tenho que entendê-lo :)
hobbs

2

PHP, 234 226 bytes

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)." ");}}

função recursiva, pega dicionário de um arquivo chamado d.
Não corresponde a todas as palavras do dicionário que não sejam letras.

Você pode usar qualquer nome de arquivo se define ("d","<filename>"); antes de chamar a função.

Adicione 2 ou 3 bytes para uma execução mais rápida:
remova $s?:print"$r\n";, insira $s!=$m?antes 0!==e :print$r.$wantes ;}}.

demolir

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

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

Notas

O ditado deve ser um arquivo chamado d. A sequência morse é passada pela linha de comando. por exemplo:

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

Para "compactação de código morse", estou usando uma árvore binária

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.