Swipe Type Converter

27

Следующая революция в наборе текста на ноутбуках была выпущена SwiftKey первого апреля 2014 года . Тем не менее, я хочу быть первым, кто напишет наноклон для просмотра, но, так как я не могу найти хороший текст в библиотеке реального текста и не могу их ждать, я спрашиваю здесь.

задача

Напишите программу, которая принимает текст свайпом и выводит эквивалент реального текста. Пример:

Input: hgrerhjklo
Output: hello

Когда пользователь делает:

введите описание изображения здесь

Другие примеры:

Input: wertyuioiuytrtjklkjhgfd
Output: world

Input: poiuytrtyuioiugrewsasdfgbhnmkijnbg
Output: programming

Input: poiuygfdzxcvhjklkjhgres
Output: puzzles

Input: cvhjioiugfde
Output: code

Input: ghiolkjhgf
Output: golf

правила

  • Программа возьмет одно слово «слово» на стандартный ввод или argv
  • Первая и последняя буква введенного пальца будет соответствовать первой и последней букве реального слова.
  • Вы можете предположить, что пользователь будет делать достаточно прямые линии, но вы можете использовать данные образца для проверки этого (я сделал данные образца, и я сделаю окончательные данные теста)
  • Для неоднозначного ввода, вы можете сделать выбор любого выхода, но я постараюсь устранить всю неоднозначность из тестовых данных
  • Это слово будет в этом списке слов (но сильно ударил). Список слов будет находиться в текущем каталоге и может быть прочитан (разделены новой строкой, будет иметь имя wordlist, без расширения).
  • Размах будет содержать только строчные буквенные символы
  • Размах может содержать дублированные символы, если пользователь делает паузу на клавише
  • Программа должна выводить на стандартный вывод (регистр не имеет значения)
  • Программа должна вернуться 0как код возврата
  • Вы должны указать команду запуска, команду компиляции (если необходимо), имя и какой входной путь использовать
  • Применяются стандартные лазейки (хотя они могут не помочь)
  • Не допускаются не встроенные библиотеки
  • Предпочтительны детерминированные, не гольфовые / запутанные решения
  • Нет записи файла, сети и т. Д.
  • Ваш код должен выполняться за одну секунду или меньше (ваш код запускается один раз за слово)
  • Оценки выполняются на процессоре Intel i7 Haswell с 4 виртуальными кодами (2 реальных), поэтому вы можете использовать потоки, если вам нужно
  • Максимальная длина кода 5000 байтов
  • Используемый язык должен иметь бесплатную (не пробную) версию для Linux (Arch Linux, если это имеет значение)

Критерий победы

  • Победителем является наиболее точное решение (оценивается контрольной программой с использованием предоставленного списка тестов)
  • Популярность это разрыв связи
  • Таблица очков будет обновляться каждые несколько дней
  • Тайм-ауты и сбои считаются сбоями
  • Это испытание будет выполняться в течение двух или более недель, в зависимости от популярности.
  • В окончательной оценке будет использоваться другой, случайно выбранный список слов (той же длины, из того же списка слов)

Другие

Текущие табло

список тестов ( логи ):

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

Финальный забег

список тестов ( логи ):

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

Молодцы всем и hgfdsasdertyuiopoiuy swertyuiopoijnhg!

matsjoyce
источник
Что такое «решение»? Где его код?
Ручка двери
Давайте продолжим эту дискуссию в чате .
Оптимизатор
@Optimizer Не уверен , что в других случаях, но « р oiuytres с т ы ы д fghui о iugfd х ГЕ я мкг гр хз sdfghjk л ка у » содержит каждую букву «парадоксальный», в порядке, за исключением , который не удваивается. l
es1024 13.10.14
1
@ Оптимизатор Ну, я думал, что твое представление побьет это, но это было чуть ниже (небольшая подстройка изменила бы это, я уверен). Кажется, я могу принять это, так что ... я должен (мне кажется, я не получаю репутацию от принятия этого)? Я хотел бы принять чужие, но это не соответствует правилам (если у вас нет хорошей идеи).
matsjoyce

Ответы:

12

JavaScript, ES6, Three Pass Optimizer, 112 187 235 240 241 243 и 231 234 прохода

Трехпроходный фильтр, который сначала определяет критические ключи в последовательности нажатий клавиш, а затем пропускает последовательность через три фильтра:

  1. Свободно сформированный RegEx из критических ключей и ключей помощи. Это дает правильный результат для большинства ключей (около 150)
  2. Строгий RegEx сделан только из критических ключей. Это дает правильный результат для дополнительных 85 последовательностей
  3. Третий фильтр, чтобы выяснить неоднозначность среди близких ответов. Это работает для 40% неоднозначных случаев.

Код

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

Код предполагает наличие переменной с именем, wordsкоторая представляет собой массив всех слов с этой страницы

Смотрите код в действии здесь

Смотрите тестовые примеры в действии здесь

Обе вышеуказанные ссылки работают только на последнем Firefox (33 и выше) (благодаря ES6).

оптимизатор
источник
Ага! Я обстреливаю снарядами. Вы также можете использовать правильный keypos.csvфайл сейчас. Функции ввода - вывода avalible перечислены в developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/...
matsjoyce
Это хорошо, но смахивания сделаны с моими углами клавиатуры, так что это ваш выбор (хотя, похоже, не повлияло на ваш счет!)
matsjoyce
Конечно сделано. github.com/matsjoyce/codegolf-swipe-type/blob/master/logs/…
matsjoyce
240 пасов выдающиеся! Я бы подумал, что неясности помешают таким хорошим результатам. Мне будет любопытно, как это будет работать на финальном тестовом наборе.
Эмиль
@ Emil - Да, я тоже жду, чтобы увидеть это.
Оптимизатор
9

Ruby, Regex Solver - 30 140 176 180 182 187 и 179 183 прохода

Я выясню счет позже. Вот очень наивное решение, которое не учитывает раскладку клавиатуры:

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]

Он принимает входные данные от ARGV и печатает результат. Я просто фильтрую список слов по первой и последней букве, а затем я пробую все оставшиеся слова по отношению к вводу (исключая повторяющиеся буквы и используя регулярное выражение, как ^g.*u.*e.*s$для «угадай») и возвращаю первое из них, если есть Есть несколько решений.

Запустите это как

ruby regex-solver.rb cvhjioiugfde

Любой другой, не стесняйтесь повторно использовать этот шаг в качестве первого фильтра - я полагаю, что он не выбрасывает правильные слова, поэтому эта предварительная проверка может значительно сократить пространство поиска для лучших алгоритмов.

Изменить: Следуя предложению ОП, я сейчас выбираю самого длинного из кандидатов, что кажется неплохой эвристикой.

Также спасибо es1024 за напоминание о дубликатах писем.

Мартин Эндер
источник
Выполнено. Ваш журнал находится на github.com/matsjoyce/codegolf-swipe-type/blob/master/logs/… Я думаю, что проблема в том, что он выбирает из возможных решений случайным образом, что можно улучшить, выбрав самое длинное или что-то еще.
matsjoyce
Я думаю, что это может привести к выбрасыванию всех правильных слов с двумя одинаковыми буквами рядом друг с другом, например paradoxically, так как они lбудут отображаться только один раз на входе, а не в два раза, как того требует регулярное выражение
es1024 13.10.14
@ es1024, ах, спасибо, когда я впервые предложил этот алгоритм в песочнице, я действительно знал об этом, но вчера забыл об этом. Исправлю позже.
Мартин Эндер
7

C ++, Дискретное расстояние Фреше - 201 220 222 232 и 232 прохода

Для меня проблема очень требовала расстояния Фреше, которое, к сожалению, очень сложно вычислить.

Ради интереса я попытался подойти к этой проблеме, применив дискретное приближение, описанное Томасом Эйтером и Хейкки Маннилой в « Вычисление дискретного расстояния Фреше» (1994).

Сначала я использую тот же подход, что и другие, для фильтрации всех слов в списке, которые являются подпоследовательностями входных данных (также учитывающих множественные последовательные вхождения одного и того же символа). Затем я заполняю кривую многоугольника от буквы к букве каждого слова промежуточными точками и сопоставляю ее с входной кривой. Наконец, я делю каждое расстояние на длину слова и беру минимальный балл.

Пока что метод работает не так хорошо, как я надеялся (он распознает пример кода как «chide»), но это может быть просто результатом ошибок, которые я еще не обнаружил. Иначе, другой идеей было бы использовать другие вариации расстояния fréchet («среднее» вместо «максимальная длина поводка»).

Редактировать: Теперь я использую приближение к "средней длине поводка собаки". Это означает, что я нахожу упорядоченное отображение между двумя путями, которое минимизирует сумму всех расстояний, а затем делит ее на количество расстояний.

Если один и тот же символ встречается в словарном слове дважды или чаще, я ставлю только один узел в пути.

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

Я скомпилировал код с помощью Clang, но он g++ ./thiscode.cpp -o ./thiscodeтакже должен работать нормально.

camelNeck
источник
1
Да! Кто-то наконец-то добавил решение с большим жирным именем алгоритма! Ваш журнал находится по адресу github.com/matsjoyce/codegolf-swipe-type/blob/master/logs/…
matsjoyce
1
Давайте скорее назовем это простым алгоритмом динамического программирования для большой жирной проблемы информатики.
camelNeck
По какой-то причине, это кажется неудачным для ввода programmijng- это дает мне pang.
Riking
Это странно. Я получаю так, programmingкак должно. Не могли бы вы раскомментировать строку // cout << thispair->first << ": " << score << endl;и вставить полученные результаты?
camelNeck
6

Python 2, Turnarounds, 224 226 230 232 и 230 234 проходов

Это довольно прямой подход:

  • Найдите точки, где поток букв меняет направление (плюс начало и конец).
  • Выведите самое длинное слово, которое включает в себя все эти поворотные точки.
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

Беги как

python turnarounds.py tyuytrghn

Решение не очень надежное. Единственное неправильное нажатие клавиши может привести к сбою. Однако, поскольку в наборе тестовых примеров очень мало ошибок, он работает довольно хорошо, запутываясь только в некоторых случаях, когда выбирает слишком длинные слова.

Редактировать: я немного изменил его, чтобы не использовать предоставленный файл положения ключа, а упрощенный макет. Это облегчает мой алгоритм, потому что в файле ключи не расположены равномерно. Я могу достичь даже чуть лучших результатов, включив некоторые специальные таймбрейкеры для неоднозначных случаев, но это будет чрезмерной оптимизацией для этого конкретного набора тестов. Остаются некоторые сбои, которые на самом деле не являются неоднозначными, но которые я не уловил с помощью моего простого алгоритма, потому что он учитывает только изменения направления более чем на 90 градусов.

Эмиль
источник
Отлично сработано! Текущий лидер! Если вы хотите увидеть журнал, Гото github.com/matsjoyce/codegolf-swipe-type/blob/master/logs/...
matsjoyce
@matsjoyce: я добавил комментарий к вопросу, указывающий на две орфографические ошибки, которые я нашел. :)
Эмиль
Да, спасибо, я просто проверяю это еще раз. Я не совсем уверен, что делать с неоднозначными случаями.
matsjoyce
@matsjoyce: некоторые неоднозначности можно устранить, выбрав другой из возможных путей через клавиатуру. Например, bratsможет быть, с 'bgrdsasdrtrds'чем нельзя спутать breasts. Впрочем, я бы тоже не возражал, если бы вы оставили все как есть.
Эмиль
Правда, это будет работать. Я просто волнуюсь, что если этот набор сделан слишком «оптимальным», а окончательный набор
оценок
6

PHP, Direction Checker, 203 213 216 229 231 и 229 233 проходов

Это начинается с простого прохода по словарю, чтобы получить список слов, чьи буквы присутствуют во входных данных (что сделал здесь Мартин ), а также только проверка l.*вместо того, l.*l.*когда lдублируется.

Самое длинное слово здесь сохраняется в переменной.

Затем выполняется еще одна проверка, основанная на клавишах в точках, где свайп меняет направление (от + / 0 до - или - / 0 до +, в x или y). Список возможных слов сверяется с этим списком символов, а слова, которые не совпадают, удаляются. Этот подход основан на резких поворотах при смахивании, чтобы быть точным.

Выводится самое длинное оставшееся слово, или, если не осталось слов, вместо него выводится самое длинное слово из предыдущего.

Редактировать: Добавлены все символы в горизонтальной линии, приводящие к изменению направления, в качестве возможных значений для проверки.

Требуется PHP 5.4 (или заменить все [..]на 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
источник
@matsjoyce Пропустил этот пункт при чтении вопроса. Код теперь использует позиции отkeypos.csv
es1024
@ es1024 Хотя список слов и не входит в 250 тестовых случаев, он содержит 238 слов с тремя последовательными буквами (пока что все, что я проверял, это слова, оканчивающиеся на sss) - я не думаю, что ваше дублирование исключит их.
Мартин Эндер
Если вам это нужно, ваши логи на github.com/matsjoyce/codegolf-swipe-type/blob/master/logs/…
matsjoyce
3

Python 3, Corner Simulator, 241 и 240 проходов

Алгоритм:

  • Дедуплицируйте (удаляйте последовательные серии одного и того же символа) ввод и все слова в списке слов (используя словарь, чтобы вернуть исходные слова)
  • Удалите все слова, которые не начинаются и не заканчиваются началом и концом пролистывания (первый проход)
  • Сделайте регулярное выражение из всех углов больше 80 градусов, затем удалите все слова, которые не соответствуют этому (второй проход)
  • Регексируйте каждое слово (например, Regex Solver) против свайпа, затем разбейте свайп на серию теоретически прямых линий и проверьте, являются ли они прямыми и могли ли они быть получены пальцем, перемещающимся по этой линии ( significant_letterфункция) (третий проход)
  • Сортировать слова по близости к прямым
  • Тогда используйте длину как прерыватель связи (чем дольше, тем лучше)
  • Затем используйте расстояние Левенштейна в качестве окончательного разрыва связи
  • Выходное слово!

Код:

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

Бежать с python3 entries/corner_sim.py

matsjoyce
источник
Это был правильный ответ. Не нужно делать мой ответ.
Оптимизатор
@Optimizer Ну, мета- обсуждение, кажется, одобряет принятие вашего ответа, так что я в порядке.
matsjoyce
Прочитав только эту мета-дискуссию, я согласился с вашим решением, но это тоже хорошо (лучше) :)
Оптимизатор