Оптимізація телефонної клавіатури


33

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

Також усі ці люди забувають, де лежить справжнє вузьке місце в сучасному спілкуванні - телефонна клавіатура.

Ось як виглядає ваша середня телефонна клавіатура:

Клавіатура телефону

Буква 'r' - третя літера на кнопці 7; тож якби ви набрали букву 'r' на мобільному телефоні, ви натискали б кнопку 7 тричі, для 's' ви натискали її 4 рази, а для 'a' ви натискали кнопку 2 один раз.

Враховуючи це, введення "e" після "d", ймовірно, було поганим рішенням - "e" - це найпоширеніша літера в англійському алфавіті, тож, якщо ви мали позначити кнопку 3 "EDF" замість "DEF", ви врятувало б досить багато натискань на клавіші.

Більше того, ви, напевно, самі переконалися, що введення двох літер, які мають одну і ту ж кнопку, викликає неприємності - якщо ви хочете написати "TU", ви не можете просто тричі натиснути 8, тому що це призведе до "V". Тому зазвичай ви пишете "T", потім натискаєте пробіл, потім натискаєте на "backspace", а потім пишете "U", що дорівнює 5 натисканням кнопок замість 3.


TL; DR

Враховуючи ці два правила:

  • Буква вводиться натисканням кнопки n разів, де n - місце, де знаходиться буква на етикетці кнопки
  • Для написання двох літер, які вводяться за допомогою однієї кнопки, потрібно додатково натиснути 2 кнопки

Який розклад клавіатури телефону вимагає найменшої кількості натискань кнопок з урахуванням конкретного тексту? Слід використовувати лише кнопки 2-9, 1 і 0, зарезервовані для спеціальних символів.

Вхідні дані

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

Вихідні дані

Я не хочу ставити занадто багато обмежень на вихід, оскільки це іноді дає деяким мовам переваги перед іншими; тож якби ваша мова показує масиви, це нормально, або ви можете розділити кожну мітку новим рядком.

Можливо кілька можливих оптимальних макетів, ви можете надрукувати будь-яку з них. Ось простий приклад:

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

Бонусні бали

-35, якщо ваш алгоритм не змушує всіх можливих макетів (я переглядаю "перестановки" Haskell тут)

-3 якщо ваш код поміститься всередині текстового повідомлення (140 символів), і ви опублікуєте фотографію з відправленням коду другові.

Це мій перший виклик на StackExchange. Буду радий почути, чи подобається вам це, чи є якісь інші відгуки про нього!


2
Ласкаво просимо на CodeGolf.SE! Я не бачу жодних проблем з вашим запитанням, але, як правило, найкраще опублікувати виклик у пісочниці, щоб отримати зворотній зв'язок та усунути неясності перед публікацією на головному сайті.
— Мартін Ендер

Ах це здорово, я точно буду в майбутньому.
— Флонк

1
Мій телефон може надіслати одне SMS з 60 символів, але дужки не підтримуються належним чином, чи я не бонус?
— ζ--

1
Приємне запитання! Я не думаю, що ніхто не зможе уникнути бонусу -35. Навіть якщо ми обмежимось макетами, що містять 4 символи на двох клавішах і 3 на всіх інших 6, є 26! / (2! * 6!) = 280,063,514,671,253,913,600,000 > 2^77унікальні перестановки, рахуючи прості перестановки клавіш лише один раз.
— Денніс

2
Також я запитую, чи могли ви люди надрукувати кількість натискань кнопок вашого рішення. Тож ми побачимо, хто знайшов найкращого!
— Антоніо Рагагнін

Відповіді:


5

Перл, 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

Ось спроба оптимізації для правила №2. Після мого коментаря вище, а замість відповідей, враховуючи це правило (пор. Високий рейтинг запитань), я подумав, що я зобов'язаний певними зусиллями тут ...

Рішення, які не оптимізуються для правила №2, можуть давати вихід дуже далекий від оптимального. Я перевірив довгий природний англійський текст ("Аліса в країні чудес", власне), попередньо оброблений (лише малі літери) та, наприклад, сценарій Perl з відповіді OJW, результат був

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

er поодинці це розорить, плюс деякі інші пари ніколи не повинні закінчуватися на одному ключі ...

Btw, zxqjvkbpfmygwculdrshnioateвідсортовані літери, частота висхідних, з цього тексту.

Якщо ми спробуємо вирішити його простим способом (сподіваючись на -35 бонус, можливо) і розміщуючи букви по черзі, вибираючи доступний ключ за мінімальною кількістю пар, ми можемо закінчити, наприклад:

slbx
hdmz
nrf
iuj
ogv
awk
tcp
eyq

Я не розміщую тут код для цього (неправильного) рішення. Наприклад, зауважте, cце частіше wі ставиться на перше місце. tc( ct) пари, очевидно, рідше, ніж ac( ca) - 43 + 235 проти 202 + 355. Але потім wзакінчується a- 598 + 88. Ми закінчуємо парами awі tc(964), хоча було б краще acі tw(635). І т.д.

Отже, наступний алгоритм намагається перевірити кожні 8 (або 2, якщо останні) останні найпоширеніші букви проти букв, які вже є на клавіатурі, і розмістити їх так, щоб кількість парних ліній була мінімальною.

$_=<>;                          # 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.

Результат:

sdfz
hlmx
nrv
iyp
ogk
acq
twb
euj

Мені не подобається acпара (зрештою, Кіт є одним із символів), але все-таки це оптимальне розміщення літер для англійської мови, якщо мій код не помиляється. Не зовсім 'гольф' зусилля, просто якесь робоче рішення, потворне чи ні.


3

Python3, це час Монтекарло!

Щоб вирішити цю проблему, спершу підраховую, скільки потрібних вам "клацань" за допомогою клавіатури за замовчуванням (по суті:) abc,def,ghi,jkl,mno,pqrs,tuv,wxyz. Потім я модифікую цю клавіатуру і бачу, чи вона дешевша (текст пишеться менше кліків). Якщо ця клавіатура дешевша, то вона називається типовою. Я повторюю цей процес 1Mрази.

Для зміни клавіатури я спочатку вирішую, скільки змін внести (максимальна кількість змін - це загальна кількість літер n клавіатури). Потім для кожного перемикача я вибираю дві кнопки та дві позиції і переношу персонаж з першої позиції на другу.

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

Вихід echo "jackdawslovemybigsphinxofquartz" | python .\myscript.py:

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

Де 61кількість натиснутих кнопок, щоб скласти певне повідомлення.

Персонажі (без пробілів і коментарів): 577

Я знаю його давно, але я дуже новачок у цьому матеріалі.

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)

Мені це було так смішно, що я вирішив спробувати цей алгоритм із LO HOBBIT (у мене є оригінальна копія і вдома!). У ній є 383964букви, і я знаходжу кілька клацань проти клавіатури :

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

Тому я стверджую, що ця остання є однією з найбільш практичних клавіатур (з точки зору клацань).


Як дізнатися, чи оптимально це?
— PyRulez

Монтекарло не працює таким чином. Це лише ставить вас ближче та ближче до оптимального рішення. Але скажімо, якщо за 1 мільйон спроб ваше рішення не зміниться .. тоді ви, мабуть, використовуєте оптимальне. (або ви недостатньо рухаєтесь від цього "місцевого мінімуму")
— Антоніо Рагагнін

Тож для викликів нам це потрібно лише для того, щоб він працював більшу частину часу тоді?
— PyRulez

1
@PyRulez Я зрозумів, що це не буде легкою проблемою знайти оптимальне рішення (за винятком випадків, якщо ви спробуєте всі можливі рішення, які я сподівався запобігти за допомогою бонуса -35), тому я насправді справді копаю цей підхід.
— Флонк

1
Цікавий метод, але, можливо, це завдання не є саме його доменом. Я перевірив, і для "Аліси" клавіатура за замовчуванням вимагає 291613 клацань. Оптимізований за моєю програмою - 195597. При підході до "Монте-Карло" я не отримав менше 207000 кліків за більш ніж 5 мільйонів ітерацій. І краще замінювати букви, тобто макет 2x4 + 6x3 залишаючись постійним.
— користувач2846289

2

Добре, якщо ви просто хочете, щоб найпопулярніші символи присвоювалися кошикам 2-9, Perl може це зробити за 127 символів ...

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

даючи щось на кшталт:

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

Або роздрукувати все в одному рядку, видаливши 12 символів:

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

2
Ви можете легко обрізати це на 100 символів:$x{$_}++for split/\s*/,<>;map$o{$n++%8}.=$_,sort{$x{$b}<=>$x{$a}}keys%x;print map"$_:".$o{$_-2},2..9
— вдень

1

Хаскелл, 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

Приклад:

$ 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"]

Можна стверджувати, що це не оптимізує для правила 2, але воно ставить найчастіші букви на різні клавіші.


0

JavaScript, 192 - 35 = 157

Щойно помітив правило, що повторюється, символи; це не враховує цього. Але як зазначив @mniip у своїй відповіді:

Можна стверджувати, що це не оптимізує для правила 2, але воно ставить найчастіші букви на різні клавіші.

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)

Можливо, це було б у Рубі, але я не вдома, і мене змушують використовувати Internet Explorer (eww). Але ей, іноді весело з використанням мов, страшних у гольфі в гольф! ;)

Приклад виводу (для вашого вводу):

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

Оскільки у JS немає STDIN, програма передбачає, що вхід зберігається в змінній s.


Чи оптимізуєте ви також з цього приводу: "Для написання двох літер, які вводяться за допомогою однієї кнопки, потрібні додаткові 2 натискання кнопок"
— Digital Trauma

Re: остання редакція. Я думаю, що результат для 'abcdefghia'цього не зовсім оптимальний.
— користувач2846289

@VadimR "Ви також можете припустити, що вхідний текст досить великий, і кожна буква є хоча б раз"
— Doorknob

Я знаю. 'azbcdefghizjklmnopqzrstuvwxyz'
— користувач2846289

1
Ви можете оптимізувати b=['','','','','','','','']до b=[x='',x,x,x,x,x,x,x], s.split('')до s.split(x)та o[x]=o[x]?o[x]+1:1до o[x]=-~o[x].
— Зубна щітка

0

Пітон (119-35 = 84):

Припустимо, що рядок є змінною a і містить лише малі літери:

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]

неозорений:

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

Ааа, ми можемо кинути величезний імпорт. Знову ж таки, це передбачає, що викреслена рядок знаходиться в a.

for h in R(8): print h+2,Z(*S([(CC(a)[d],d) for d in Se(a)])[::-1])[1][h::8]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.