Otimizando o teclado do telefone


33

Parece haver essa mania contínua de as pessoas aprenderem tediosamente novos layouts de teclado como Dvorak ou Neo, porque supostamente os torna mais produtivos. Argumento que mudar os layouts de teclado é uma péssima idéia, pois pode levar meses para você acelerar, e quando você é 5% mais rápido que o resto, fica ferrado se precisar digitar em um computador que não seja é seu.

Além disso, todas essas pessoas esquecem onde está o verdadeiro gargalo na comunicação moderna - o teclado do telefone.

É assim que o teclado de um telefone comum se parece:

O teclado do telefone

A letra 'r' é a terceira letra no botão 7; portanto, se você digitar a letra 'r' em um celular, pressione o botão 7 três vezes, para 's' pressione 4 vezes e para 'a' pressione o botão 2 uma vez.

Considerando isso, colocar 'e' depois de 'd' provavelmente foi uma má decisão - 'e' é a letra mais usada no alfabeto inglês; portanto, se você rotular o botão 3 "EDF" em vez de "DEF", você economizaria bastante pressionamentos de tecla.

Além disso, você provavelmente já experimentou que digitar 2 letras que compartilham o mesmo botão é um incômodo - se você quiser escrever "TU", não poderá pressionar 8 três vezes, porque isso resultaria em 'V'. Geralmente, você escreve 'T', pressiona o espaço, pressiona o backspace e depois escreve 'U', que é igual a 5 pressionamentos de botão em vez de 3.


TL; DR

Dadas estas duas regras:

  • Uma letra é digitada pressionando um botão n vezes, em que n é a posição em que a letra está no rótulo do botão
  • Escrever duas letras digitadas usando o mesmo botão requer mais 2 pressionamentos de botão

Qual é o layout do teclado do telefone que requer o menor número de pressionamentos de botão, considerando um texto específico? Você deve usar apenas os botões 2-9, 1 e 0, reservados para símbolos especiais.

Entrada

O texto para o qual você deve encontrar o layout ideal é fornecido via stdin. Você não precisa manipular nada além do alfabeto em minúsculas e pode assumir que a entrada consiste apenas nisso. Você também pode assumir que o texto de entrada é razoavelmente grande e que todas as letras estão lá pelo menos uma vez, se isso ajudar.

Saída

Não quero impor muitas restrições à saída, pois isso às vezes dá vantagens a alguns idiomas em relação a outros; portanto, no entanto, seu idioma mostra que as matrizes são boas. Como alternativa, você pode separar cada rótulo com uma nova linha.

Pode haver vários layouts ideais possíveis, você pode imprimir qualquer um deles. Aqui está um exemplo simples:

>> echo "jackdawslovemybigsphinxofquartz" | foo.sh
ojpt
avhz
cen
skm
dyf
wbq
ixu
lgr

Pontos bônus

-35 se o seu algoritmo não forçar brutalmente todos os layouts possíveis (eu estou vendo as permutações de Haskell aqui)

-3 se o seu código couber em uma mensagem de texto (140 caracteres) e você publica uma foto sua enviando o código a um amigo.

Este é o meu primeiro desafio no StackExchange. Ficaria feliz em saber se você gosta ou tem algum outro feedback sobre isso!


2
Bem-vindo ao CodeGolf.SE! Não vejo nenhum problema com sua pergunta, mas geralmente é uma boa idéia postar seu desafio na sandbox primeiro para obter feedback e remover ambiguidades antes de postar no site principal.
Martin Ender

Ah, isso é legal, eu definitivamente irei no futuro.
`` Flonk

1
Meu telefone pode enviar um único SMS de 60 caracteres, mas não suporta colchetes corretamente, estou sem o bônus?
---- 22/03

1
Boa pergunta! Eu não acho que alguém será capaz de evitar o bônus -35. Mesmo se nos restringirmos a layouts com 4 caracteres em duas das teclas e 3 em todas as 6 restantes, existem 26! / (2! * 6!) = 280,063,514,671,253,913,600,000 > 2^77permutações únicas, contando reorganizações simples das teclas apenas uma vez.
Dennis

2
Além disso, pergunto se vocês podem imprimir o número de botões pressionados na sua solução. Então, vamos ver quem encontrou o melhor!
Antonio Ragagnin 30/03

Respostas:


5

Perl, 333

$_=<>;$c{$&}++while/./g;@c=sort{$c{$b}<=>$c{$a}}keys%c;$d{$&.$1}++while/.(?=(.))/g;sub f{my$x=shift;if(my$c=pop@$x){for(grep!$_[$_],0..7){my@y = @_;$y[$_]=$c;f([@$x],@y)}}else{for(0..7){$z=$_[$_];$c+=$d{$z.$_}+$d{$_.$z}for@{$a[$_]}}$c<$m?($m=$c,@n=@_):1}}while(@c){$m= ~0;f[splice@c,0,8];push@{$a[$_]},$n[$_]for 0..7}print@$_,$/for@a

Aqui está uma tentativa de otimizar a regra nº 2. Após o meu comentário, acima, e em vez de respostas que levaram essa regra em consideração (cf. classificação alta das perguntas), pensei que devo algum esforço aqui ...

Soluções que não otimizam para a regra nº 2 podem produzir resultados muito longe do ideal. Eu verifiquei o texto longo em inglês natural ("Alice no País das Maravilhas", na verdade), pré-processado (apenas letras minúsculas) e, por exemplo, o script Perl da resposta da OJW, sendo o resultado

2: ermx
3: tdfz
4: alp
5: oub
6: ick
7: nwv
8: hgj
9: syq

er só o arruina, e alguns outros pares nunca deveriam ter terminado na mesma chave ...

Entre, zxqjvkbpfmygwculdrshnioatesão letras ordenadas, com frequência crescente, a partir desse texto.

Se tentarmos resolver da maneira mais fácil (esperando um bônus de -35, talvez) e colocar as letras uma a uma, escolhendo a chave disponível pela contagem mínima em pares, podemos terminar com, por exemplo:

slbx
hdmz
nrf
iuj
ogv
awk
tcp
eyq

Não publico código para esta solução (errada) aqui. Por exemplo, observe que cé mais frequente we é colocado em primeiro lugar. tcOs ctpares ( ) são obviamente menos frequentes que ac( ca) - 43 + 235 contra 202 + 355. Mas então wacaba com a- 598 + 88. Terminamos com pares awe tc(964 no total), embora fosse melhor ace tw(635 no total). Etc ..

Portanto, o próximo algoritmo tenta verificar cada 8 letras restantes (ou 2, se houver) mais frequentes contra as que já estão no teclado e colocá-las para que a contagem entre pares seja mínima.

$_=<>;                          # Read STDIN.
$c{$&}++while/./g;              # Count letters (%c hash).
@c=sort{$c{$b}<=>$c{$a}}keys%c; # Sort them by frequency, ascending
$d{$&.$1}++while/.(?=(.))/g;    # (@c array), and count pairs (%d hash).

                                # Next is recursive sub that does the job.
                                # Some CPAN module for permutations
                                # would probably do better...
                                # Arguments are reference to array of what's 
                                # left un-placed of current 8-pack of letters,
sub f{                          # and 8 element list of placed letters
    my$x=shift;                 # (or undefs).
    if(my$c=pop@$x){            # Pop a letter from 8-pack (if anything left),
        for(grep!$_[$_],0..7){  # try placing it on each available key, and 
            my@y = @_;          # call sub again passing updated arguments.
            $y[$_]=$c;
            f([@$x],@y)
        }
    }else{                      # If, OTOH, 8-pack is exhausted, find sum of
        for(0..7){              # pairs count of current permutation (@_) and 
            $z=$_[$_];          # letters placed in previous rounds (8-packs).
                                # @a is "array of arrays" - note, we didn't 
                                # have to initialize it. First "8-pack" will
                                # be placed on empty keypad "automatically".
                                # We re-use undefined (i.e. 0) $c.

            $c+=$d{$z.$_}+$d{$_.$z}for@{$a[$_]}
        }
        $c<$m                   # Is sum for current placement minimal?
            ?($m=$c,@n=@_)      # Then remember this minimum and placement.
            :1
    }
}

while(@c){
    $m= ~0;                         # Initialize "minimum" with large enough 
    f[splice@c,0,8];                # number, then call sub with each 8-pack
                                    # (and empty list of placed letters 
                                    # from current round). On return,
                                    # @n will have optimal arrangement.
    push@{$a[$_]},$n[$_]for 0..7    # Then place it permanently on keypad.
}
print@$_,$/for@a                    # Show us what you've done.

O resultado é:

sdfz
hlmx
nrv
iyp
ogk
acq
twb
euj

Eu não gosto do acpar (afinal, o gato é um dos personagens), mas ainda é o posicionamento ideal das letras para o inglês, se meu código não estiver errado. Não é exatamente o esforço do 'golfe', apenas uma solução de trabalho, feia ou não.


3

Python3, é hora de Montecarlo!

Para resolver esse problema, primeiro conto quantos "cliques" você precisa com o teclado padrão (inicialmente:) abc,def,ghi,jkl,mno,pqrs,tuv,wxyz. Depois modifico este teclado e vejo se é mais barato (o texto é escrito em menos cliques). Se esse teclado for mais barato, ele se tornará o padrão. Eu repito esse processo 1Mvezes.

Para alterar o teclado, primeiro decido quantas alterações serão feitas (o número máximo de alterações é o número total de letras no teclado). Então, para cada chave, escolho dois botões e duas posições e transfiro um caractere da primeira posição para a segunda.

O número máximo de opções por vez é o número de letras no teclado, pois é o número mínimo de alterações necessárias para alternar de dois teclados diferentes. Quero que seja sempre possível alternar de um teclado para outro.

A saída de echo "jackdawslovemybigsphinxofquartz" | python .\myscript.pyé:

61 ['anb', 'sef', 'hjc', 'iykl', 'odm', 'qgr', 'tuxv', 'wpz']

Onde 61está o número de botão pressionado para compor uma determinada mensagem.

Personagens (sem espaços e sem comentários): 577

Eu sei que é longo, mas sou realmente novo nessas coisas.

from random import *
S=['abc','def','ghi','jkl','mno','pqrs','tuv','wxyz']
def P(L): # perform a switch of the keys of the keyboard:to switch from a given keyboard to another, the maximum number of exchanges is the number of the keys.
    R=randint
    N = len(''.join(L))
    W = randint(1,N)   # decide how many switches to perform
    EL = list(L)
    for i in range(0,W):
        B1=R(0,len(EL)-1)   # decide what buttons are considered in the switch
        B2=R(0,len(EL)-1)
        if len(EL[B1])==0: continue   
        P1=R(0,len(EL[B1])-1)       # decide what letter to switch and where
        if len(EL[B2])==0: P2=0
        else:   P2=R(0,len(EL[B2])-1)
        C1 = EL[B1][P1]     
        EL[B1]=EL[B1].replace(C1,'')
        EL[B2]=EL[B2][:P2]+C1+EL[B2][P2:]
    return EL
def U(L,X): # count how many clicks you need to compose the text X
    S=0
    Z=' '
    for A in X:
        for T in L:
            if A in T and Z not in T: S+=1+T.index(A)
            if A in T and Z in T: S+=3+T.index(A) # if the last character was in the same button..here the penality!
        Z=A
    return S
X=input()
n_iter=10**6
L = list(S)
cc=U(L,X)
print(cc,L)
for i in range(0,n_iter): #do some montecarlo stuff
    cc=U(L,X)
    pl=P(L)
    pc=U(pl,X)
    if(cc>pc):
        L=pl 
        print(pc,L)

Achei tão engraçado que decidi tentar esse algoritmo com o LO HOBBIT (também tenho uma cópia original em casa!). Tem 383964letras e estes são os cliques e o teclado que estou encontrando:

909007 ['abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz']
879344 ['abkc', 'def', 'gqhi', 'jl', 'mno', 'rs', 'tupv', 'wxyz']
861867 ['abg', 'def', 'qhyi', 'jcl', 'mno', 'r', 'tupxv', 'swkz']
851364 ['abg', 'e', 'qchi', 'jyl', 'mn', 'dr', 'tupxv', 'sowkfz']
829451 ['ag', 'ef', 'qchi', 'jyl', 'mn', 'dbr', 'tupxv', 'sowkz']
815213 ['amg', 'ef', 'qch', 'ojyl', 'i', 'dbnr', 'tupxv', 'swkz']
805521 ['amg', 'ef', 'ch', 'ojyl', 'qi', 'dbnr', 'tupxv', 'swkz']
773046 ['amg', 'ef', 'ch', 'ojyl', 'qi', 'bnr', 'tupxv', 'dswkz']
759208 ['amg', 'eqf', 'ch', 'ojyl', 'i', 'bnr', 'tupxv', 'dswkz']
746711 ['ag', 'ekq', 'clh', 'sojy', 'bi', 'nmfr', 'tupxv', 'dwz']
743541 ['ag', 'ekq', 'clh', 'sojy', 'bi', 'nmfr', 'tpxv', 'dwuz']
743389 ['ag', 'ekq', 'clh', 'sojy', 'i', 'nmfr', 'tpxbv', 'dwuz']
734431 ['ag', 'ekq', 'lh', 'sjy', 'ci', 'nrf', 'tpxbv', 'dowumz']
705730 ['ag', 'oekq', 'lh', 'sjy', 'ci', 'nrf', 'tpxbv', 'dwumz']
691669 ['ag', 'oekq', 'lh', 'nsjy', 'ic', 'rf', 'tpxbv', 'dwumz']
665866 ['ag', 'hokq', 'el', 'nsjy', 'ic', 'rbf', 'tpxv', 'dwumz']
661610 ['agm', 'hokq', 'e', 'nsj', 'ilc', 'rbf', 'tpyxv', 'dwuz']

Portanto, afirmo que este último é um dos teclados mais práticos (em termos de cliques).


Como você sabe se é ideal?
PyRulez

Montecarlo não funciona dessa maneira. Apenas o coloca cada vez mais perto da solução ideal. Mas, digamos, se em 1 milhão de tentativas sua solução não mudar ... então provavelmente você está usando a solução ideal. (ou você não está se movendo o suficiente deste "mínimo local") #
Antonio Ragagnin 30/03

Então, para os desafios, precisamos apenas trabalhar a maior parte do tempo?
PyRulez

1
@PyRulez Percebi que esse não seria um problema fácil de encontrar uma solução ideal (exceto se você tentar todas as soluções possíveis, as quais eu esperava evitar com esse bônus de -35), então realmente entendo essa abordagem.
Flonk 31/03

1
Método interessante, mas talvez essa tarefa não seja exatamente o seu domínio. Eu verifiquei e, para 'Alice', o teclado padrão requer 291613 cliques. Otimizado com o meu programa - 195597. Com a abordagem 'Monte Carlo', não recebi menos de 207000 cliques em mais de 5 milhões de iterações. E é melhor trocar letras, ou seja, o layout 2x4 + 6x3 permanece constante.
user2846289

2

Bem, se você quer apenas os personagens mais populares atribuídos às posições 2-9, o Perl pode fazer isso em 127 caracteres ...

foreach(split /\s*/,<>){$x{$_}++}
foreach(sort{$x{$b}<=>$x{$a}}keys %x){$o{$n++%8}.=$_}
for(0..7){printf "%d: %s\n",$_+2,$o{$_}}

dando algo como:

echo "jackdawslovemybigsphinxofquartz" | perl ./keypad.pl
2: ajeb
3: iynz
4: suv
5: ohm
6: wkl
7: rgp
8: xfc
9: dtq

Ou imprima tudo em uma linha, removendo 12 caracteres:

foreach(split /\s*/,<>){$x{$_}++}
foreach(sort{$x{$b}<=>$x{$a}}keys %x){$o[$n++%8].=$_}
print join",",values@o,"\n";

2
você pode facilmente aparar isso para 100 caracteres:$x{$_}++for split/\s*/,<>;map$o{$n++%8}.=$_,sort{$x{$b}<=>$x{$a}}keys%x;print map"$_:".$o{$_-2},2..9
ardnew 23/03

1

Haskell, 160 - 35 = 125

import Data.List
import GHC.Exts
main=interact f where f s=show$transpose$map($sortWith(\x->length$filter(/=x)s)['a'..'z'])[t,t.d,t.d.d,d.d.d];t=take 8;d=drop 8

Exemplo:

$ runhaskell % <<< "jackdaws loves my big sphinx of quartz"
["afpy","sgqz","ihr","ojt","bku","clv","dmw","enx"]
$ </usr/share/dict/propernames tr A-Z a-z | runhaskell % 
["atjx","edgq","rhb","nmp","iyv","lcf","ouw","skz"]

Pode-se argumentar que isso não é otimizado para a regra 2, mas coloca as letras mais frequentes em teclas diferentes .


0

JavaScript, 192 - 35 = 157

Apenas notei a regra dos caracteres repetidos; isso não leva isso em consideração. Mas como @mniip observou em sua resposta:

Pode-se argumentar que isso não é otimizado para a regra 2, mas coloca as letras mais frequentes em teclas diferentes .

o={}
a=[]
b=['','','','','','','','']
i=-1
s.split('').forEach(function(x){o[x]=o[x]?o[x]+1:1})
for(x in o)a.push([o[x],x])
a.sort().reverse().forEach(function(x){b[i=(i+1)%8]+=x[1]})
alert(b)

Provavelmente isso deveria estar no Ruby, mas não estou em casa e estou sendo forçado a usar o Internet Explorer (eww). Mas, ei, às vezes é divertido usar idiomas terríveis no golfe para jogar golfe! ;)

Saída de amostra (para sua entrada):

avlc,sukb,otj,irh,zqg,ypf,xne,wmd

Desde JS não tem STDIN, o programa assume que a entrada é armazenada na variável s.


Você também está otimizando com isso em mente: "Escrever duas letras digitadas usando o mesmo botão requer mais 2 pressionamentos de botão"
Digital Trauma

Re: última edição. Eu acho que a saída para 'abcdefghia'não é exatamente ideal.
user2846289

@VadimR "Você também pode assumir que o texto de entrada é razoavelmente grande e cada letra é lá pelo menos uma vez"
Doorknob

Eu sei. 'azbcdefghizjklmnopqzrstuvwxyz'
user2846289

1
Você pode otimizar b=['','','','','','','','']para b=[x='',x,x,x,x,x,x,x], s.split('')para s.split(x)e o[x]=o[x]?o[x]+1:1para o[x]=-~o[x].
Escova de dentes

0

Python (119-35 = 84):

Supondo que a string seja uma variável ae contenha apenas letras minúsculas:

for h in range(8): print h+2,zip(*sorted([(__import__("collections").Counter(a)[d],d) for d in set(a)])[::-1])[1][h::8]

ungolfed:

import collections

#a="jackdawslovemybigsphinxofquartz"
a=__import__("string").lowercase

b=collections.Counter(a)

c=set(a)

d=[(b[d],d) for d in c]

e=sorted(d)

f=e[::-1]

g=zip(*f)[1]

for h in range(8): print h+2,g[h::8]

PYG (76-35 = 41):

Aaah, podemos deixar cair a importação enorme. Novamente, isso pressupõe que a seqüência de caracteres despojada esteja em a.

for h in R(8): print h+2,Z(*S([(CC(a)[d],d) for d in Se(a)])[::-1])[1][h::8]
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.