Swipe Type Converter


27

La prossima rivoluzione nella digitazione su laptop è stata lanciata il primo aprile 2014 da SwiftKey . Tuttavia, voglio essere la prima persona a scrivere un nano clone, ma, poiché non riesco a trovare un buon testo scorrevole nella libreria di testi reali e non posso aspettarli, chiedo qui.

Compito

Scrivi un programma che includa il testo scorrevole e produca l'equivalente del testo reale. Esempio:

Input: hgrerhjklo
Output: hello

Quando l'utente fa:

inserisci qui la descrizione dell'immagine

Altri esempi:

Input: wertyuioiuytrtjklkjhgfd
Output: world

Input: poiuytrtyuioiugrewsasdfgbhnmkijnbg
Output: programming

Input: poiuygfdzxcvhjklkjhgres
Output: puzzles

Input: cvhjioiugfde
Output: code

Input: ghiolkjhgf
Output: golf

Regole

  • Il programma prenderà una 'parola' trascinata dentro su stdin o argv
  • La prima e l'ultima lettera dell'ingresso trascinato corrisponderanno alla prima e all'ultima lettera della parola reale
  • Puoi presumere che l'utente realizzerà linee ragionevolmente dritte, ma puoi utilizzare i dati di esempio per verificarlo (ho creato i dati di esempio e realizzerò i dati di prova finali)
  • Per input ambigui, è possibile selezionare uno dei due output, ma proverò a sradicare tutte le ambiguità dai dati di test
  • Questa parola sarà in questo elenco di parole (ma trascinato). L'elenco di parole sarà nella directory corrente e può essere letto (separato da una nuova riga, verrà chiamato wordlist, senza estensione).
  • Il colpo conterrà solo caratteri alfabetici minuscoli
  • Il colpo può contenere caratteri duplicati, se l'utente fa una pausa su un tasto
  • Il programma deve essere emesso su stdout (il caso non ha importanza)
  • Il programma deve restituire 0come codice di ritorno
  • È necessario fornire il comando di esecuzione, il comando di compilazione (se necessario), il nome e il percorso di input da utilizzare
  • Si applicano scappatoie standard (potrebbero non essere d'aiuto, però)
  • Non sono consentite librerie non integrate
  • Preferibili soluzioni deterministiche, non golfate / offuscate
  • Nessuna scrittura di file, rete, ecc.
  • Il codice deve essere eseguito in un secondo o meno (il codice viene eseguito una volta per parola)
  • Le esecuzioni di punteggio vengono eseguite su un processore Intel i7 Haswell, con 4 codici virtuali (2 reali), quindi puoi usare i thread se devi
  • Lunghezza massima del codice di 5000 byte
  • La lingua che usi deve avere una versione gratuita (non di prova) disponibile per Linux (Arch Linux, se questo è importante)

Criterio vincente

  • Il vincitore è la soluzione più accurata (segnata dal programma di controllo , utilizzando l'elenco di test fornito)
  • La popolarità è il pareggio
  • La tabella dei punteggi verrà aggiornata ogni pochi giorni
  • I timeout e gli arresti anomali contano come falliti
  • Questa sfida durerà due settimane o più, a seconda della popolarità
  • Il punteggio finale utilizzerà un elenco di parole diverso, selezionato casualmente (stessa lunghezza, dallo stesso elenco di parole)

Altro

Tabelle dei punteggi attuali

lista di test ( registri ):

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

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

Corsa finale

lista di test ( registri ):

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

Complimenti a tutti e hgfdsasdertyuiopoiuy swertyuiopoijnhg!


Che cos'è "Una soluzione"? Dov'è il suo codice?
Maniglia della porta



@Optimizer Non sono sicuro degli altri casi, ma " p ouytres a se r es a s d fghui o iugfd x cgu i ug c xs a sdfghjk l ku y " contiene ogni lettera di "paradossalmente", in ordine, ad eccezione di l, che non è raddoppiato.
es1024,

1
@Optimiser Beh, pensavo che la tua richiesta sarebbe stata superata, ma era appena sotto (un po 'di modifiche lo avrebbero cambiato, ne sono sicuro). Sembra che posso accettarlo, quindi ... dovrei (non mi sembra di ottenere un rappresentante dall'accettarlo)? Vorrei accettare qualcun altro, ma questo non segue le regole (a meno che tu non abbia una buona idea).
matsjoyce,

Risposte:


12

JavaScript, ES6, ottimizzatore a tre passaggi, 112 187 235 240 241 243 e 231 234 passaggi

Un filtro a tre passaggi che prima individua i tasti critici nella sequenza di tasti e poi passa la sequenza attraverso i tre filtri:

  1. Un RegEx liberamente formato dalle chiavi critiche e dalle chiavi di aiuto. Questo dà il risultato corretto per la maggior parte dei tasti (circa 150)
  2. Un RegEx rigoroso fatto solo di chiavi critiche. Questo dà il risultato corretto per ulteriori 85 sequenze
  3. Un terzo filtro per capire l'ambiguità tra le risposte ravvicinate. Funziona con casi ambigui al 40%.

Codice

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

Il codice presuppone che wordssia presente una variabile chiamata che è un array di tutte le parole di questa pagina

Guarda il codice in azione qui

Guarda i casi di test in azione qui

Entrambi i link sopra funzionano solo su un ultimo Firefox (33 e versioni successive) (a causa di ES6).


Sì! Sto sborsando con conchiglie. Puoi anche usare il keypos.csvfile corretto ora. Le funzioni IO disponibili sono elencate su developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/…
matsjoyce

Va bene, ma i passaggi sono fatti con le mie angolazioni della tastiera, quindi è una tua scelta (non sembra aver influenzato il tuo punteggio, però!)
matsjoyce


240 passaggi è eccezionale! Avrei pensato che le ambiguità avrebbero impedito risultati così buoni. Sarò curioso di sapere come si comporterà sul set di test finale.
Emil,

@Emil - Sì, aspetto anch'io di vederlo.
Ottimizzatore

9

Ruby, Risolutore Regex - 30 140 176 180 182 187 e 179 183 passaggi

Capirò il punteggio più tardi. Ecco una soluzione molto ingenua che non tiene conto del layout della tastiera:

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]

Prende input da ARGV e stampa il risultato. Sto solo filtrando l'elenco delle parole per prima e ultima lettera, e loro sto provando tutte le parole rimanenti contro l'input (eliminando le lettere duplicate e usando una regex come ^g.*u.*e.*s$per "indovinare") e restituendo la prima di quelle se presenti sono molteplici soluzioni.

Eseguilo come

ruby regex-solver.rb cvhjioiugfde

Chiunque altro, non esiti a riutilizzare questo passaggio come primo filtro - credo che non eliminerà le parole corrette, quindi questo controllo preliminare può ridurre notevolmente lo spazio di ricerca per algoritmi migliori.

Modifica: seguendo il suggerimento dei PO, ora sto selezionando il più lungo dei candidati, che sembra essere un euristico decente.

Grazie anche a es1024 per avermi ricordato le lettere duplicate.


Fatto. Il tuo registro è su github.com/matsjoyce/codegolf-swipe-type/blob/master/logs/… Penso che il problema sia che seleziona casualmente le possibili soluzioni, che potrebbero essere migliorate selezionando la più lunga o qualcos'altro.
matsjoyce,

Penso che questo potrebbe eliminare tutte le parole corrette con due lettere identiche l'una accanto all'altra, come ad esempio paradoxically, lapparirebbero solo una volta nell'input, piuttosto che due volte come richiesto dal regex.
es1024,

@ es1024, ah grazie, quando ho proposto per la prima volta questo algoritmo nella sandbox, in realtà ne ero a conoscenza, ma ieri me ne sono dimenticato. Risolverà più tardi.
Martin Ender,

7

C ++, distanza discreta di Fréchet - 201 220 222 232 e 232 passaggi

Per me, il problema richiedeva molto la distanza di Fréchet, che purtroppo è molto difficile da calcolare.

Solo per divertimento, ho cercato di affrontare il problema implementando un'approssimazione discreta descritta da Thomas Eiter e Heikki Mannila in Computing Discrete Fréchet Distance (1994).

All'inizio, sto usando lo stesso approccio degli altri per filtrare tutte le parole dell'elenco che sono sottosequenze dell'input (tenendo conto anche delle ricorrenze multiple dello stesso carattere). Quindi, sto riempiendo la curva poligonale da lettera a lettera di ogni parola con punti intermedi e la confronto con la curva di input. Infine, divido ogni distanza per la lunghezza della parola e prendo il punteggio minimo.

Finora, il metodo non funziona come avevo sperato (riconosce l'esempio di codice come "chide"), ma questo potrebbe essere solo il risultato di bug che non ho ancora trovato. Altrimenti, un'altra idea sarebbe quella di utilizzare altre variazioni della distanza tra le punte ("media" anziché "lunghezza massima del guinzaglio").

Modifica: ora sto usando un'approssimazione alla "lunghezza media del guinzaglio del cane". Ciò significa che sto trovando una mappatura ordinata tra i due percorsi che minimizza la somma di tutte le distanze e successivamente la divide per il numero di distanze.

Se lo stesso carattere appare due o più volte nella parola del dizionario, inserisco solo un nodo nel percorso.

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

Ho compilato il codice con clang, ma g++ ./thiscode.cpp -o ./thiscodedovrebbe anche funzionare bene.


1
Sì! Qualcuno ha finalmente aggiunto una soluzione con un grosso nome di algoritmo grasso! Il tuo registro è su github.com/matsjoyce/codegolf-swipe-type/blob/master/logs/…
matsjoyce,

1
Chiamiamolo piuttosto un semplice algoritmo di programmazione dinamica per un grosso problema di informatica.
CamelNeck,

Per qualche ragione, questo sembra fallire per l'input di programmijng- mi dà pang.
Riking

È strano. Sto diventando programmingcome dovrebbe. Potresti per favore decommentare la linea // cout << thispair->first << ": " << score << endl;e incollare i punteggi risultanti?
CamelNeck

6

Python 2, Turnaround, 224 226 230 232 e 230 234 passaggi

Questo è un approccio piuttosto diretto:

  • Trova i punti in cui il flusso di lettere cambia direzione (più inizio e fine).
  • Stampa la parola più lunga che include tutti questi punti di svolta.
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

Correre come

python turnarounds.py tyuytrghn

La soluzione non è molto solida. Un singolo tasto sbagliato potrebbe far fallire. Tuttavia, poiché l'insieme dei casi di test non ha pochissimi errori di ortografia, si comporta abbastanza bene, confondendosi solo in alcuni casi in cui prende parole troppo lunghe.

Modifica: l' ho modificato un po 'per non utilizzare il file di posizione chiave fornito ma un layout semplificato. Questo semplifica il mio algoritmo perché nel file le chiavi non sono equidistanti. Posso ottenere risultati anche leggermente migliori includendo alcuni tiebreaker ad hoc per casi ambigui, ma sarebbe un'ottimizzazione eccessiva per questo particolare set di test. Restano alcuni fallimenti che non sono in realtà ambigui ma che non afferro al mio semplice algoritmo perché considera solo i cambi di direzione di oltre 90 gradi.


Molto bene! Leader attuale! Se vuoi vedere il registro,
vai a

@matsjoyce: ho aggiunto un commento alla domanda sottolineando i due errori di ortografia che penso di aver trovato. :)
Emil,

Sì, grazie, sto solo dando un altro assegno. Non sono del tutto sicuro di cosa fare con casi ambigui.
matsjoyce,

@matsjoyce: alcune ambiguità potrebbero essere risolte scegliendo un altro dei possibili percorsi attraverso la tastiera. Ad esempio bratspotrebbe essere 'bgrdsasdrtrds'che non può essere confuso con breasts. Tuttavia, non mi dispiacerebbe nemmeno se l'avessi lasciato così com'è.
Emil,

Vero, funzionerebbe. Sono solo preoccupato che se questo set è reso troppo "ottimale", e il set di punteggio finale non è sottoposto a un sacco di controllo, alcune soluzioni potrebbero non funzionare altrettanto bene
matsjoyce

6

Passaggi PHP, Direction Checker, 203 213 216 229 231 e 229 233

Questo inizia con un semplice passaggio attraverso il dizionario per ottenere un elenco di parole le cui lettere sono tutte presenti nell'input (cosa che ha fatto Martin qui ), e anche per controllare solo al l.*posto di l.*l.*quando lviene duplicato.

La parola più lunga qui viene quindi salvata in una variabile.

Viene quindi eseguito un altro controllo, basato sui tasti nei punti in cui il colpo cambia direzione (da + / 0 a - o - / da 0 a +, in xo y). L'elenco di parole possibili viene verificato rispetto a questo elenco di caratteri e le parole che non corrispondono vengono eliminate. Questo approccio si basa su curve strette nello scorrimento per essere precisi.

Viene emessa la parola più lunga rimanente oppure, se non vengono lasciate parole, viene emessa invece la parola più lunga di prima.

Modifica: aggiunti tutti i caratteri in una linea orizzontale che porta a un cambio di direzione come possibili valori da verificare.

È richiesto PHP 5.4 (o sostituisci tutto [..]con 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;

@matsjoyce Ho perso quel punto elenco durante la lettura della domanda. Il codice ora utilizza le posizioni dalkeypos.csv
es1024 del

@ es1024 Anche se non fa parte dei 250 casi di test, l'elenco delle parole contiene 238 parole con tre lettere consecutive (finora, tutto quello che ho controllato sono le parole che finiscono in sss) - Non penso che la tua eliminazione duplicata catturerebbe quelle.
Martin Ender,


3

Python 3, Corner Simulator, 241 e 240 passaggi

Algoritmo:

  • Deduplica (rimuovi sequenze consecutive dello stesso carattere) l'input e tutte le parole nell'elenco delle parole (usando un dizionario per recuperare le parole originali)
  • Rimuovi tutte le parole che non iniziano e finiscono con l'inizio e la fine del colpo (primo passaggio)
  • Crea una regex da tutti gli angoli superiori a 80 gradi, quindi rimuovi tutte le parole che non corrispondono (secondo passaggio)
  • Registra ogni parola (come Regex Solver) contro lo scorrimento, quindi dividi lo scorrimento in una serie di linee teoricamente dritte e controlla se sono dritte e potrebbero essere state prodotte da un dito che si muove lungo quella linea ( significant_letterfunzione) (terzo passaggio)
  • Ordina le parole per vicinanza alle linee rette
  • Quindi utilizzare la lunghezza come pareggio (più lungo è meglio)
  • Quindi utilizzare la distanza Levenshtein come ultimo pareggio
  • Parola di uscita!

Codice:

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

Corri con python3 entries/corner_sim.py


Questa è stata una risposta valida Non è necessario che la mia sia la risposta.
Ottimizzatore

@Optimizer Bene, la meta discussione sembra favorire l'accettazione della tua risposta, quindi va bene per me.
matsjoyce,

Dopo aver letto solo quella meta discussione, stavo bene con la tua decisione, ma anche questo è buono (meglio) :)
Ottimizzatore
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.