Swipe Type Converter

27

A próxima revolução na digitação em laptops foi lançada no dia primeiro de abril de 2014 pela SwiftKey . No entanto, quero ser a primeira pessoa a escrever um nano clone deslizante, mas como não consigo encontrar um bom texto de furto na biblioteca de texto real e não posso esperar por eles, pergunto aqui.

Tarefa

Escreva um programa que receba texto de furto e produza o equivalente em texto real. Exemplo:

Input: hgrerhjklo
Output: hello

Quando o usuário faz:

insira a descrição da imagem aqui

Outros exemplos:

Input: wertyuioiuytrtjklkjhgfd
Output: world

Input: poiuytrtyuioiugrewsasdfgbhnmkijnbg
Output: programming

Input: poiuygfdzxcvhjklkjhgres
Output: puzzles

Input: cvhjioiugfde
Output: code

Input: ghiolkjhgf
Output: golf

Regras

  • O programa incluirá uma palavra 'swiped' em stdin ou argv
  • A primeira e a última letra da entrada swiped serão iguais à primeira e à última letra da palavra real
  • Você pode assumir que o usuário fará linhas razoavelmente retas, mas você pode usar os dados da amostra para verificar isso (eu fiz os dados da amostra e os dados finais do teste)
  • Para entrada ambígua, você pode selecionar qualquer saída, mas tentarei erradicar toda ambiguidade dos dados de teste
  • Esta palavra estará nesta lista de palavras (mas passou o dedo). A lista de palavras estará no diretório atual e pode ser lida (nova linha separada, será nomeada wordlist, sem extensão).
  • O furto conterá apenas caracteres alfabéticos em minúsculas
  • O furto pode conter caracteres duplicados, se o usuário pausar em uma tecla
  • O programa deve produzir em stdout (caso não importe)
  • O programa deve retornar 0como o código de retorno
  • Você deve fornecer o comando de execução, comando de compilação (se necessário), nome e qual caminho de entrada usar
  • Aplicam-se brechas padrão (embora possam não ajudar)
  • Não são permitidas bibliotecas não integradas
  • Soluções determinísticas, não-golfe / ofuscadas preferidas
  • Sem gravação de arquivo, rede, etc.
  • Seu código deve ser executado em um segundo ou menos (seu código é executado uma vez por palavra)
  • As execuções de pontuação são executadas em um processador Intel i7 Haswell, com 4 códigos virtuais (2 reais), para que você possa usar threads se precisar
  • Comprimento máximo do código de 5000 bytes
  • O idioma usado deve ter uma versão gratuita (sem teste) disponível para Linux (Arch Linux, se isso importa)

Critério vencedor

  • O vencedor é a solução mais precisa (pontuada pelo programa de controle , usando a lista de testes fornecida)
  • A popularidade é o desempate
  • A tabela de pontuação será atualizada a cada poucos dias
  • Tempos limite e falhas contam como falhas
  • Esse desafio dura duas semanas ou mais, dependendo da popularidade
  • A pontuação final usará uma lista de palavras diferente e selecionada aleatoriamente (mesmo tamanho, da mesma lista de palavras)

De outros

Current Score Boards

lista de teste ( logs ):

Three Pass Optimizer:Errors: 0/250       Fails: 7/250        Passes: 243/250     Timeouts: 0/250     
Corner Sim:         Errors: 0/250       Fails: 9/250        Passes: 241/250     Timeouts: 0/250     
Discrete Fréchet Distance:Errors: 0/250       Fails: 17/250       Passes: 233/250     Timeouts: 0/250     
Turnaround:         Errors: 0/250       Fails: 18/250       Passes: 232/250     Timeouts: 0/250     
Direction Checker:  Errors: 0/250       Fails: 19/250       Passes: 231/250     Timeouts: 0/250     
Regex Solver:       Errors: 0/250       Fails: 63/250       Passes: 187/250     Timeouts: 0/250

testlist2 ( logs ):

Corner Sim:         Errors: 0/250       Fails: 10/250       Passes: 240/250     Timeouts: 0/250     
Three Pass Optimizer:Errors: 2/250       Fails: 14/250       Passes: 234/250     Timeouts: 0/250     
Turnaround:         Errors: 0/250       Fails: 16/250       Passes: 234/250     Timeouts: 0/250     
Direction Checker:  Errors: 0/250       Fails: 17/250       Passes: 233/250     Timeouts: 0/250     
Discrete Fréchet Distance:Errors: 0/250       Fails: 18/250       Passes: 232/250     Timeouts: 0/250     
Regex Solver:       Errors: 0/250       Fails: 67/250       Passes: 183/250     Timeouts: 0/250

Final Run

lista de teste ( logs ):

Corner Sim:         Errors: 0/250       Fails: 14/250       Passes: 236/250     Timeouts: 0/250     
Three Pass Optimizer:Errors: 0/250       Fails: 18/250       Passes: 232/250     Timeouts: 0/250     
Direction Checker:  Errors: 0/250       Fails: 20/250       Passes: 230/250     Timeouts: 0/250     
Turnaround:         Errors: 0/250       Fails: 23/250       Passes: 227/250     Timeouts: 0/250     
Discrete Fréchet Distance:Errors: 0/250       Fails: 30/250       Passes: 220/250     Timeouts: 0/250     
Regex Solver:       Errors: 0/250       Fails: 55/250       Passes: 195/250     Timeouts: 0/250

Bem feito a todos e hgfdsasdertyuiopoiuy swertyuiopoijnhg!

matsjoyce
fonte
O que é "uma solução"? Onde está o código?
Maçaneta
@Optimizer Não tenho certeza sobre os outros casos, mas " p oiuytres um se r es a s d fghui o iugfd x CGU i ug c xs um sdfghjk l ku y " contém todas as letras de "paradoxalmente", em ordem, exceto para o l, que não é dobrado.
es1024
1
@ Optimiser Bem, eu pensei que sua apresentação fosse superada, mas estava logo abaixo (um pequeno ajuste teria mudado isso, tenho certeza). Parece que posso aceitá-lo, então ... devo (eu não pareço receber representante por aceitá-lo)? Gostaria de aceitar outra pessoa, mas isso não está seguindo as regras (a menos que você tenha uma boa ideia).
precisa saber é o seguinte

Respostas:

12

JavaScript, ES6, Three Pass Optimizer, 112 187 235 240 241 243 e 231 234 passes

Um filtro de três passagens que primeiro descobre chaves críticas na sequência de pressionamento de tecla e depois passa a sequência pelos três filtros:

  1. Um RegEx vagamente formado a partir das chaves críticas e das chaves auxiliares. Isso fornece o resultado correto para a maioria das teclas (cerca de 150)
  2. Um RegEx estrito feito apenas de chaves críticas. Isso fornece o resultado correto para 85 sequências extras
  3. Um terceiro filtro para descobrir ambiguidade entre respostas próximas. Isso funciona para 40% de casos ambíguos.

Código

keyboard = {
  x: {},
  y: ['  q      w      e      r      t      y      u      i      o      p',
      '    a      s      d      f      g      h      j      k      l',
      '        z      x      c      v      b      n      m'],
};
for (var i in keyboard.y)
  for (var y of keyboard.y[i])
    keyboard.x[y] = +i*7;
p = C => (x=keyboard.x[C],{x, y: keyboard.y[x/7].indexOf(C)})
angle = (L, C, R) => (
  p0 = p(L), p1 = p(C), p2 = p(R),
  a = Math.pow(p1.x-p0.x,2) + Math.pow(p1.y-p0.y,2),
  b = Math.pow(p1.x-p2.x,2) + Math.pow(p1.y-p2.y,2),
  c = Math.pow(p2.x-p0.x,2) + Math.pow(p2.y-p0.y,2),
  Math.acos((a+b-c) / Math.sqrt(4*a*b))/Math.PI*180
)
corner = (L, C, R, N, W) => {
  if (skip) {
    skip = false;
    return [];
  } 
  ngl = angle(L, C, R);
  if (ngl < 80) return [C + "{1,3}"]
  if (ngl < 115 && p(L).x != p(R).x && p(L).x != p(C) && p(R).x != p(C).x && Math.abs(p(L).y - p(R).y) < 5) return [C + "{0,3}"]
  if (ngl < 138) {
    if (N && Math.abs(ngl - angle(C, R, N)) < 6) {
      skip = true;
      return [L + "{0,3}", "([" + C + "]{0,3}|[" + R + "]{0,3})?", N + "{0,3}"]
    }
    return [C + "{0,3}"]
  }
  return ["([" + L + "]{0,3}|[" + C + "]{0,3}|[" + R + "]{0,3})?"]
}
f = S => {
  for (W = [S[0] + "{1,2}"],i = 1; i < S.length - 1; i++)
    W.push(...corner(S[i - 1], S[i], S[i + 1], S[i + 2], W))
  return [
    new RegExp("^" + W.join("") + S[S.length - 1] + "{1,3}$"),
    new RegExp("^" + W.filter(C=>!~C.indexOf("[")).join("") + S[S.length - 1] + "{1,3}$")
  ]
}
thirdPass = (F, C) => {
  if (!F[0]) return null
  F = F.filter((s,i)=>!F[i - 1] || F[i - 1] != s)
  FF = F.map(T=>[...T].filter((c,i)=>!T[i - 1] || T[i - 1] != c).join(""))
  if (FF.length == 1) return F[0];
  if (FF.length < 6 && FF[0][2] && FF[1][2] && FF[0][0] == FF[1][0] && FF[0][1] == FF[1][1])
    if (Math.abs(F[0].length - F[1].length) < 1)
      for (i=0;i<Math.min(F[0].length, FF[1].length);i++) {
        if (C.indexOf(FF[0][i]) < C.indexOf(FF[1][i])) return F[0]
        else if (C.indexOf(FF[0][i]) > C.indexOf(FF[1][i])) return F[1]
      }
  return F[0]
}
var skip = false;
SwiftKey = C => (
  C = [...C].filter((c,i)=>!C[i - 1] || C[i - 1] != c).join(""),
  skip = false, matched = [], secondPass = [], L = C.length, reg = f(C),
  words.forEach(W=>W.match(reg[0])&&matched.push(W)),
  words.forEach(W=>W.match(reg[1])&&secondPass.push(W)),
  matched = matched.sort((a,b)=>Math.abs(L-a.length)>Math.abs(L-b.length)),
  secondPass = secondPass.sort((a,b)=>Math.abs(L-a.length)>Math.abs(L-b.length)),
  first = matched[0], second = secondPass[0], third = thirdPass(secondPass.length? secondPass: matched, C),
  second && second.length >= first.length - 1? first != third ? third: second: third.length >= first.length ? third: first
)

// For use by js shell of latest firefox
print(SwiftKey(readline()));

O código assume que uma variável chamada wordsestá presente, que é uma matriz de todas as palavras desta página

Veja o código em ação aqui

Veja os casos de teste em ação aqui

O link acima funciona apenas em um Firefox mais recente (33 e superior) (devido ao ES6).

Optimizer
fonte
Sim! Estou descascando com conchas. Você também pode usar o keypos.csvarquivo apropriado agora. As funções IO Avalible estão listados no developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/...
matsjoyce
É bom, mas os golpes são feitos com meus ângulos de teclado, por isso é sua escolha (não parece ter afetado a sua pontuação, embora!)
matsjoyce
240 passes é excelente! Eu teria pensado que ambiguidades impediriam resultados tão bons. Ficarei curioso para saber como isso será executado no conjunto de testes final.
Emil
@Emil - Sim, estou esperando para ver isso também.
Optimizer
9

Ruby, Regex Solver - 30 140 176 180 182 187 e 179 183 passes

Eu vou descobrir a pontuação mais tarde. Aqui está uma solução muito ingênua que não leva em conta o layout do teclado:

words = File.readlines('wordlist').map(&:chomp)

swipe = ARGV.shift
puts words.select {|word| word[0] == swipe[0] &&
                          word[-1] == swipe[-1]}
          .select {|word|
              chars = [word[0]]
              (1..word.size-1).each {|i| chars << word[i] if word[i] != word[i-1]}
              swipe[Regexp.new('^'+chars.join('.*')+'$')]
          }.sort_by {|word| word.size}[-1]

Ele recebe a entrada do ARGV e imprime o resultado. Estou apenas filtrando a lista de palavras pela primeira e pela última letra, e elas estou tentando todas as palavras restantes contra a entrada (eliminando letras duplicadas e usando um regex como ^g.*u.*e.*s$para "palpite") e retornando a primeira delas, se houver são várias soluções.

Execute como

ruby regex-solver.rb cvhjioiugfde

Qualquer outra pessoa, sinta-se à vontade para reutilizar esta etapa como primeiro filtro - acredito que ela não emitirá nenhuma palavra correta, portanto, essa verificação preliminar pode reduzir bastante o espaço de pesquisa para obter melhores algoritmos.

Edit: Seguindo a sugestão dos OPs, agora estou selecionando o mais longo dos candidatos, o que parece ser uma heurística decente.

Também obrigado a es1024 por me lembrar sobre letras duplicadas.

Martin Ender
fonte
Feito. Seu registro está em github.com/matsjoyce/codegolf-swipe-type/blob/master/logs/…. Acho que o problema é que ele seleciona as possíveis soluções aleatoriamente, o que pode ser melhorado selecionando a mais longa ou outra coisa.
matsjoyce
Eu acho que isso pode jogar todas as palavras corretas com duas letras idênticas próximas uma da outra, como paradoxically, pois as lapareceriam apenas uma vez na entrada, em vez de duas vezes o exigido pelo regex.
es1024
@ es1024, ah obrigado, quando propus esse algoritmo pela primeira vez na caixa de areia, eu estava realmente ciente disso, mas esqueci ontem. Vai consertar mais tarde.
Martin Ender
7

C ++, distância discreta de Fréchet - 201 220 222 232 e 232 passes

Para mim, o problema exigia muito a Distância Fréchet, que infelizmente é muito difícil de calcular.

Apenas por diversão, tentei abordar o problema implementando uma aproximação discreta descrita por Thomas Eiter e Heikki Mannila em Computing Discrete Fréchet Distance (1994).

No começo, estou usando a mesma abordagem que as outras para filtrar todas as palavras na lista que são subsequências da entrada (também são responsáveis ​​por várias ocorrências sequenciais do mesmo caractere). Então, estou preenchendo a curva de polígono de letra a letra de cada palavra com pontos intermediários e comparando-a com a curva de entrada. Finalmente, divido cada distância pelo comprimento da palavra e obtenho a pontuação mínima.

Até agora, o método não funciona tão bem quanto eu esperava (ele reconhece o exemplo de código como "chide"), mas isso poderia ser apenas o resultado de bugs que ainda não encontrei. Senão, outra idéia seria usar outras variações da distância do fréchet ("média" em vez de "comprimento máximo da trela do cão").

Edit: Agora, eu estou usando uma aproximação para o "comprimento médio da trela do cão". Isso significa que estou encontrando um mapeamento ordenado entre os dois caminhos, o que minimiza a soma de todas as distâncias e depois o dividimos pelo número de distâncias.

Se o mesmo caractere aparecer duas ou mais vezes na palavra do dicionário, colocarei apenas um nó no caminho.

#include<iostream>
#include<fstream>
#include<vector>
#include<map>
#include<algorithm>
#include<utility>
#include<cmath>

using namespace std;

const double RESOLUTION = 3.2;

double dist(const pair<double, double>& a, const pair<double, double>& b) {
    return sqrt((a.first - b.first) * (a.first - b.first) + (a.second - b.second) * (a.second - b.second));
}

double helper(const vector<pair<double, double> >& a,
        const vector<pair<double, double> >& b,
        vector<vector<double> >& dp,
        int i,
        int j) {
    if (dp[i][j] > -1)
        return dp[i][j];
    else if (i == 0 && j == 0)
        dp[i][j] = dist(a[0], b[0]);
    else if (i > 0 && j == 0)
        dp[i][j] = helper(a, b, dp, i - 1, 0) +
                   dist(a[i], b[0]);
    else if (i == 0 && j > 0)
        dp[i][j] = helper(a, b, dp, 0, j - 1) +
                   dist(a[0], b[j]);
    else if (i > 0 && j > 0)
        dp[i][j] = min(min(helper(a, b, dp, i - 1, j),
                           helper(a, b, dp, i - 1, j - 1)),
                       helper(a, b, dp, i, j - 1)) +
                   dist(a[i], b[j]);
    return dp[i][j];
}

double discretefrechet(const vector<pair<double, double> >& a, const vector<pair<double, double> >& b) {
    vector<vector<double> > dp = vector<vector<double> >(a.size(), vector<double>(b.size(), -1.));
    return helper(a, b, dp, a.size() - 1, b.size() - 1);
}

bool issubsequence(string& a, string& b) {
    // Accounts for repetitions of the same character: hallo subsequence of halo
    int i = 0, j = 0;
    while (i != a.size() && j != b.size()) {
        while (a[i] == b[j])
            ++i;
        ++j;
    }
    return (i == a.size());
}

int main() {
    string swipedword;
    cin >> swipedword;

    ifstream fin;
    fin.open("wordlist");
    map<string, double> candidatedistance = map<string, double>();
    string readword;
    while (fin >> readword) {
        if (issubsequence(readword, swipedword) && readword[0] == swipedword[0] && readword[readword.size() - 1] == swipedword[swipedword.size() - 1]) {
            candidatedistance[readword] = -1.;
        }
    }
    fin.close();

    ifstream fin2;
    fin2.open("keypos.csv");
    map<char, pair<double, double> > keypositions = map<char, pair<double, double> >();
    char rc, comma;
    double rx, ry;
    while (fin2 >> rc >> comma >> rx >> comma >> ry) {
        keypositions[rc] = pair<double, double>(rx, ry);
    }
    fin2.close();

    vector<pair<double, double> > swipedpath = vector<pair<double, double> >();
    for (int i = 0; i != swipedword.size(); ++i) {
        swipedpath.push_back(keypositions[swipedword[i]]);
    }

    for (map<string, double>::iterator thispair = candidatedistance.begin(); thispair != candidatedistance.end(); ++thispair) {
        string thisword = thispair->first;
        vector<pair<double, double> > thispath = vector<pair<double, double> >();
        for (int i = 0; i != thisword.size() - 1; ++i) {
            pair<double, double> linestart = keypositions[thisword[i]];
            pair<double, double> lineend = keypositions[thisword[i + 1]];
            double linelength = dist(linestart, lineend);
            if (thispath.empty() || linestart.first != thispath[thispath.size() - 1].first || linestart.second != thispath[thispath.size() - 1].second)
                thispath.push_back(linestart);
            int segmentnumber = linelength / RESOLUTION;
            for (int j = 1; j < segmentnumber; ++j) {
                thispath.push_back(pair<double, double>(linestart.first + (lineend.first - linestart.first) * ((double)j) / ((double)segmentnumber),
                                    linestart.second + (lineend.second - lineend.second) * ((double)j) / ((double)segmentnumber)));
            }
        }
        pair<double, double> lastpoint = keypositions[thisword[thisword.size() - 1]];
        if (thispath.empty() || lastpoint.first != thispath[thispath.size() - 1].first || lastpoint.second != thispath[thispath.size() - 1].second)
        thispath.push_back(lastpoint);

        thispair->second = discretefrechet(thispath, swipedpath) / (double)(thispath.size());
    }

    double bestscore = 1e9;
    string bestword = "";
    for (map<string, double>::iterator thispair = candidatedistance.begin(); thispair != candidatedistance.end(); ++thispair) {
        double score = thispair->second;
        if (score < bestscore) {
            bestscore = score;
            bestword = thispair->first;
        }
        // cout << thispair->first << ": " << score << endl;
    }
    cout << bestword << endl;

    return 0;
}

Eu compilei o código com clang, mas g++ ./thiscode.cpp -o ./thiscodetambém deve funcionar bem.

camelNeck
fonte
1
Sim! Alguém finalmente adicionou uma solução com um grande nome de algoritmo gordo! Seu log está em github.com/matsjoyce/codegolf-swipe-type/blob/master/logs/…
matsjoyce 13/14
1
Vamos chamá-lo de um algoritmo de programação dinâmica simples para um grande problema de ciência da computação.
camelNeck
Por alguma razão, isso parece falhar na entrada de programmijng- isso me dá pang.
Riking 15/10
Isso é estranho. Estou ficando programmingcomo deveria. Você poderia descomentar a linha // cout << thispair->first << ": " << score << endl;e colar as pontuações resultantes?
camelNeck
6

Python 2, Turnarounds, 224 226 230 232 e 230 234 passes

Essa é uma abordagem bastante direta:

  • Encontre os pontos em que o fluxo de letras muda de direção (mais o início e o fim).
  • Emita a palavra mais longa que inclui todos esses pontos de virada.
import sys, csv, re

wordlistfile = open('wordlist', 'r')
wordlist = wordlistfile.read().split('\n')

layout = 'qwertyuiop asdfghjkl  zxcvbnm'
keypos = dict((l, (2*(i%11)+i/11, i/11)) for i,l in enumerate(layout))

#read input from command line argument
input = sys.argv[1]

#remove repeated letters
input = ''.join(x for i,x in enumerate(input) if i==0 or x!=input[i-1])

#find positions of letters on keyboard
xpos = map(lambda l: keypos[l][0], input)
ypos = map(lambda l: keypos[l][1], input)

#check where the direction changes (neglect slight changes in x, e.g. 'edx')
xpivot = [(t-p)*(n-t)<-1.1 for p,t,n in zip(xpos[:-2], xpos[1:-1], xpos[2:])]
ypivot = [(t-p)*(n-t)<0 for p,t,n in zip(ypos[:-2], ypos[1:-1], ypos[2:])]
pivot = [xp or yp for xp,yp in zip(xpivot, ypivot)]

#the first and last letters are always pivots
pivot = [True] + pivot + [True]

#build regex
regex = ''.join(x + ('+' if p else '*') for x,p in zip(input, pivot))
regexobj = re.compile(regex + '$')

#find all words that match the regex and output the longest one
words = [w for w in wordlist if regexobj.match(w)]
output = max(words, key=len) if words else input
print output

Correr como

python turnarounds.py tyuytrghn

A solução não é muito robusta. Um único pressionamento de tecla errado faria com que falhasse. No entanto, como o conjunto de casos de teste tem muito pouco erro de ortografia, ele executa muito bem, ficando confuso em alguns casos em que escolhe palavras muito longas.

Edit: Eu mudei um pouco para não usar o arquivo de posição-chave fornecido, mas um layout simplificado. Isso facilita o meu algoritmo, porque no arquivo as chaves não são espaçadas igualmente. Posso obter resultados ainda um pouco melhores incluindo alguns desempatadores ad-hoc para casos ambíguos, mas isso seria otimizador demais para esse conjunto de testes em particular. Permanecem algumas falhas que não são realmente ambíguas, mas que eu não entendo com meu algoritmo simples porque ele considera apenas mudanças de direção superiores a 90 graus.

Emil
fonte
Bem feito! Líder atual! Se você quiser ver o log, vá para github.com/matsjoyce/codegolf-swipe-type/blob/master/logs/…
matsjoyce
@ matsjoyce: adicionei um comentário à pergunta apontando os dois erros ortográficos que acho que encontrei. :)
Emil
Sim, obrigado, estou apenas dando outro cheque. Não tenho muita certeza do que fazer com casos ambíguos.
matsjoyce
@ matsjoyce: Algumas ambiguidades podem ser resolvidas escolhendo um outro dos caminhos possíveis no teclado. Por exemplo, bratspoderia ser o 'bgrdsasdrtrds'que não pode ser confundido breasts. No entanto, eu também não me importaria se você deixasse como está.
Emil
É verdade que isso funcionaria. Eu só estou preocupado que, se este conjunto é feito também 'ideal', eo conjunto de pontuação final não é colocar até muito escrutínio, algumas soluções podem não funcionar tão bem
matsjoyce
6

PHP, Checker Checker, 203 213 216 229 231 e 229 233 passes

Isso começa com uma simples passagem pelo dicionário para obter uma lista de palavras cujas letras estão presentes na entrada (o que Martin fez aqui ), e também apenas procurando em l.*vez de l.*l.*quandol é duplicada.

A palavra mais longa aqui é salva em uma variável.

Outra verificação é feita, com base nas teclas nos pontos em que o furto muda de direção (+ / 0 a - ou - / 0 a +, em x ou y). A lista de palavras possíveis é verificada nessa lista de caracteres e as palavras que não correspondem são eliminadas. Essa abordagem depende de curvas acentuadas no deslize para ser precisa.

A palavra restante mais longa é emitida ou, se nenhuma palavra for deixada, a palavra mais longa anterior é emitida.

Editar: Adicionado todos os caracteres em uma linha horizontal que leva a uma mudança de direção como possíveis valores a serem verificados.

É necessário o PHP 5.4 (ou substitua todos [..]por array(..)).

<?php
function get_dir($a, $b){
    $c = [0, 0];
    if($a[0] - $b[0] < -1.4) $c[0] = 1;
    else if($a[0] - $b[0] > 1.4) $c[0] = -1;
    if($a[1] < $b[1]) $c[1] = 1;
    else if($a[1] > $b[1]) $c[1] = -1;
    return $c;
}
function load_dict(){
    return explode(PHP_EOL, file_get_contents('wordlist'));
}

$coord = [];
$f = fopen('keypos.csv', 'r');
while(fscanf($f, "%c, %f, %f", $c, $x, $y)){
    $coord[$c] = [$x, $y];  
}
fclose($f);

$dict = load_dict();
$in = $argv[1];
$possib = [];

foreach($dict as $c){
    if($c[0] == $in[0]){
        $q = strlen($c);
        $r = strlen($in);
        $last = '';
        $fail = false;
        $i = $j = 0;
        for($i = 0; $i < $q; ++$i){
            if($last == $c[$i]) continue;
            if($j >= $r){
                $fail = true;
                break;
            }
            while($c[$i] != $in[$j++])
                if($j >= $r){
                    $fail = true; 
                    break;
                }
            if($fail) break;
            $last = $c[$i];
        }
        if(!$fail) 
            $possib[] = $c;
    }
}

$longest = '';
foreach($possib as $p){
    if(strlen($p) > strlen($longest))
        $longest = $p;
}

$dir = [[0, 0]];
$cdir = [0, 0];
$check = '/^' . $in[0] . '.*?';
$olst = []; $p = 1;

$len = strlen($in);
for($i = 1; $i < $len; ++$i){
    $dir[$i] = get_dir($coord[$in[$i - 1]], $coord[$in[$i]]);
    $olddir = $cdir;
    $newdir = $dir[$i];
    $xc = $olddir[0] && $newdir[0] && $newdir[0] != $olddir[0];
    $yc = $olddir[1] && $newdir[1] && $newdir[1] != $olddir[1];
    if($xc || $yc){ // separate dir from current dir
        if($yc && !$xc && $dir[$i - 1][1] == 0){
            $tmp = '';
            for($j = $i - 1; $j >= 0 && $dir[$j][1] == 0; --$j){
                $tmp = '(' . $in[$j-1] . '.*?)?' . $tmp;
            }
            $olst[] = range($p, $p + (($i - 1) - $j));
            $p += ($i - 1) - $j + 1;
            $check .= $tmp . '(' . $in[$i - 1] . '.*?)?';
        }else{
            $check .= $in[$i - 1] . '.*?';
        }
        $cdir = $dir[$i];
    }else{
        if(!$cdir[0]) $cdir[0] = $dir[$i][0]; 
        if(!$cdir[1]) $cdir[1] = $dir[$i][1]; 
    }
}
$check .= substr($in, -1) . '$/';
$olstc = count($olst);
$res = [];
foreach($possib as $p){
    if(preg_match($check, $p, $match)){
        if($olstc){
            $chk = array_fill(0, $olstc, 0);
            for($i = 0; $i < $olstc; ++$i){
                foreach($olst[$i] as $j){
                    if(isset($match[$j]) && $match[$j]){
                        ++$chk[$i];
                    }
                }
                // give extra weight to the last element of a horizontal run
                if(@$match[$olst[$i][count($olst[$i])-1]])
                    $chk[$i] += 0.5;
            }
            if(!in_array(0, $chk)){
                $res[$p] = array_sum($chk);
            }
        }else{
            $res[$p] = 1;
        }
    }
}
$possib = array_keys($res, @max($res));
$newlong = '';
foreach($possib as $p){
    if(strlen($p) > strlen($newlong))
        $newlong = $p;
}
if(strlen($newlong) == 0) echo $longest;
else echo $newlong;
es1024
fonte
@matsjoyce Perdeu esse ponto de bala ao ler a pergunta. O código agora usa as posições dekeypos.csv
es1024
@ es1024 Embora não faça parte dos 250 casos de teste, a lista de palavras contém 238 palavras com três letras consecutivas (até agora, tudo o que verifiquei foram as palavras que terminam em sss) - não acho que sua eliminação duplicada as capturasse.
Martin Ender
Se você precisar, seus logs em github.com/matsjoyce/codegolf-swipe-type/blob/master/logs/…
matsjoyce
3

Python 3, Corner Simulator, 241 e 240 passes

Algoritmo:

  • Desduplicar (remover execuções consecutivas do mesmo caractere) a entrada e todas as palavras da lista de palavras (usando um dicionário para recuperar as palavras originais)
  • Remova todas as palavras que não começam e terminam com o início e o fim do furto (primeira passagem)
  • Faça uma regex com todos os cantos acima de 80 graus e remova todas as palavras que não coincidirem com isso (segunda passagem)
  • Regex cada palavra (como o Regex Solver) contra o furto, depois divida o furto em uma série de linhas teoricamente retas e verifique se são retas e poderiam ter sido produzidas por um dedo movendo-se nessa linha (significant_letter função) (terceira passagem)
  • Classifique as palavras por proximidade às linhas retas
  • Em seguida, use o comprimento como desempatador (quanto mais, melhor)
  • Em seguida, use a distância de Levenshtein como desempate final
  • Palavra de saída!

Código:

# Corner Sim

from math import atan, degrees, pi, factorial, cos, radians
import csv
import re
import sys

keys = {}
key_size = 1.5
for line in open("keypos.csv"):
    k, x, y = map(str.strip, line.split(","))
    keys[k] = float(x), float(y)


def deduplicate(s):
    return s[0] + "".join(s[i + 1] for i in range(len(s) - 1) if s[i + 1] != s[i])


def angle(coord1, coord2):
    x1, y1, x2, y2 = coord1 + coord2
    dx, dy = x2 - x1, y1 - y2
    t = abs(atan(dx/dy)) if dy else pi / 2
    if dx >= 0 and dy >= 0: a = t
    elif dx >= 0 and dy < 0: a = pi - t
    elif dx < 0 and dy >= 0: a = 2 * pi - t
    else: a = t + pi
    return degrees(a)


def significant_letter(swipe):
    if len(swipe) <= 2: return 0
    x1, y1, x2, y2 = keys[swipe[0]] + keys[swipe[-1]]
    m = 0 if x2 == x1 else (y2 - y1) / (x2 - x1)
    y = lambda x: m * (x - x1) + y1
    def sim_fun(x2, y2):
        try: return (x2 / m + y2 - y1 + x1 * m) / (m + 1 / m)
        except ZeroDivisionError: return x2
    dists = []
    for i, key in enumerate(swipe[1:-1]):
        sx, bx = min(x1, x2), max(x1, x2)
        pos_x = max(min(sim_fun(*keys[key]), bx), sx)
        sy, by = min(y1, y2), max(y1, y2)
        pos_y = max(min(y(pos_x), by), sy)
        keyx, keyy = keys[key]
        dist = ((keyx - pos_x) ** 2 + (keyy - pos_y) ** 2) ** 0.5
        a = angle((keyx, keyy), (pos_x, pos_y))
        if 90 <= a <= 180: a = 180 - a
        elif 180 <= a <= 270: a = a - 180
        elif 270 <= a <= 360: a = 360 - a
        if a > 45: a = 90 - a
        h = key_size / 2 / cos(radians(a))
        dists.append((max(dist - h, 0), i + 1, key))
    return sorted(dists, reverse=True)[0][0]


def closeness2(s, t):
    # https://en.wikipedia.org/wiki/Levenshtein_distance
    if s == t: return 0
    if not len(s): return len(t)
    if not len(t): return len(s)
    v0 = list(range(len(t) + 1))
    v1 = list(range(len(t) + 1))
    for i in range(len(s)):
        v1[0] = i + 1
        for j in range(len(t)):
            cost = 0 if s[i] == t[j] else 1
            v1[j + 1] = min(v1[j] + 1, v0[j + 1] + 1, v0[j] + cost)
        for j in range(len(v0)):
            v0[j] = v1[j]
    return v1[len(t)] / len(t)


def possible(swipe, w, s=False):
    m = re.match("^" + "(.*)".join("({})".format(i) for i in w) + "$", swipe)
    if not m or s:
        return bool(m)
    return closeness1(swipe, w) < 0.8


def closeness1(swipe, w):
    m = re.match("^" + "(.*)".join("({})".format(i) for i in w) + "$", swipe)
    unsigpatches = []
    groups = m.groups()
    for i in range(1, len(groups), 2):
        unsigpatches.append(groups[i - 1] + groups[i] + groups[i + 1])
    if debug: print(unsigpatches)
    sig = max(map(significant_letter, unsigpatches))
    if debug: print(sig)
    return sig


def find_closest(swipes):
    level1, level2, level3, level4 = swipes
    if debug: print("Loading words...")
    words = {deduplicate(i.lower()): i.lower() for i in open("wordlist").read().split("\n") if i[0] == level1[0] and i[-1] == level1[-1]}
    if debug: print("Done first filter (start and end):", words)
    r = re.compile("^" + ".*".join(level4) + "$")
    pos_words2 = list(filter(lambda x: bool(r.match(x)), words))
    if debug: print("Done second filter (sharpest points):", pos_words2)
    pos_words = pos_words2 or words
    pos_words = list(filter(lambda x: possible(level1, x), pos_words)) or list(filter(lambda x: possible(level1, x, s=True), pos_words))
    if debug: print("Done third filter (word regex):", pos_words)
    sorted_pos_words = sorted((closeness1(level1, x), -len(x), closeness2(level1, x), x)
                              for x in pos_words)
    if debug: print("Closeness matching (to", level2 + "):", sorted_pos_words)
    return words[sorted_pos_words[0][3]]


def get_corners(swipe):
    corners = [[swipe[0]] for i in range(4)]
    last_dir = last_char = None
    for i in range(len(swipe) - 1):
        dir = angle(keys[swipe[i]], keys[swipe[i + 1]])
        if last_dir is not None:
            d = abs(last_dir - dir)
            a_diff = min(360 - d, d)
            corners[0].append(last_char)
            if debug: print(swipe[i], dir, last_dir, a_diff, end=" ")
            if a_diff >= 55:
                if debug: print("C", end=" ")
                corners[1].append(last_char)
            if a_diff >= 65:
                if debug: print("M", end=" ")
                corners[2].append(last_char)
            if a_diff >= 80:
                if debug: print("S", end=" ")
                corners[3].append(last_char)
            if debug: print()
        last_dir, last_char = dir, swipe[i + 1]
    tostr = lambda x: deduplicate("".join(x + [swipe[-1]]).lower())
    return list(map(tostr, corners))


if __name__ == "__main__":
    debug = "-d" in sys.argv
    if debug: sys.argv.remove("-d")
    swipe = deduplicate(sys.argv[1] if len(sys.argv) > 1 else input()).lower()
    corners = get_corners(swipe)
    if debug: print(corners)
    print(find_closest(corners))

Correr com python3 entries/corner_sim.py

matsjoyce
fonte
Esta foi uma resposta válida. Não há necessidade de fazer a minha a resposta.
Optimizer
@ Otimizador Bem, a meta- discussão parece favorecer a aceitação de sua resposta, então tudo bem por mim.
precisa saber é o seguinte
Depois de ler apenas a discussão meta, eu estava bem com sua decisão, mas isso também é bom (melhor) :)
Optimizer