滑动式转换器


27

SwiftKey在2014年4月1日发布了笔记本电脑打字的下一次革命。但是,我想成为第一个编写可擦写纳米克隆的人,但是,由于找不到适合实际文本库的可擦写文本,而且我等不及了,所以我在这里问。

任务

编写一个程序,该程序接受划动文本并输出等效的实文本。例:

Input: hgrerhjklo
Output: hello

当用户这样做时:

在此处输入图片说明

其他例子:

Input: wertyuioiuytrtjklkjhgfd
Output: world

Input: poiuytrtyuioiugrewsasdfgbhnmkijnbg
Output: programming

Input: poiuygfdzxcvhjklkjhgres
Output: puzzles

Input: cvhjioiugfde
Output: code

Input: ghiolkjhgf
Output: golf

规则

  • 该程序将在stdin或argv上输入一个刷过的“单词”
  • 滑动输入的首个字母和最后一个字母将等于真实单词的首个字母和最后一个字母
  • 您可以假设用户将做出合理的直线,但是您可以使用样本数据来验证这一点(我制作了样本数据,然后制作了最终的测试数据)
  • 对于模棱两可的输入,您可以选择任一输出,但是我将尝试消除测试数据中的所有模棱两可
  • 该单词将在单词列表中(但会被刷掉)。单词列表将在当前目录中,并且可以读取(换行符分隔,将命名为wordlist,无扩展名)。
  • 滑动仅包含小写字母字符
  • 如果用户在按键上暂停,则滑动可能包含重复的字符
  • 程序必须在stdout上输出(大小写无关紧要)
  • 程序必须0作为返回码返回
  • 您必须提供运行命令,编译命令(如果需要),名称以及要使用的输入路径
  • 存在标准漏洞(尽管可能没有帮助)
  • 不允许非内置库
  • 确定性,非高尔夫/模糊解决方案优先
  • 没有文件写入,联网等
  • 您的代码必须在一秒钟或更短的时间内运行(您的代码每个单词运行一次)
  • 计分运行在具有4个虚拟代码(2个真实代码)的Intel i7 Haswell处理器上运行,因此如果需要,您可以使用线程
  • 最大代码长度为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日志):

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做得好!


什么是“解决方案”?它的代码在哪里?
门把手



@Optimizer不知道的其他情况,但“ p oiuytres 一个 SE [R ES 一个小号d fghui Ø iugfd X CGU 微克Ç XS 一个 sdfghjk Ÿ ”包含有序的“矛盾”,每一个字母,除了l,没有翻倍。
es1024

1
@Optimiser好吧,我以为您的提交会胜过它,但是它就在下面(我敢肯定,稍作调整就会改变这一点)。看来我可以接受,所以...我应该(我似乎没有从接受它的代表那里得到回报)吗?我想接受别人的同意,但这不遵守规则(除非您有个好主意)。
matsjoyce 2014年

Answers:


12

JavaScript,ES6,三遍优化器,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文件。IO函数avalible列出在developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/...
matsjoyce

很好,但是滑动是由我的键盘角度完成的,所以这是您的选择(不过,这似乎并没有影响您的得分!)
matsjoyce 2014年


240个通行证非常出色!我以为模棱两可会阻止这样好的结果。我很好奇这将如何在最终测试集中执行。
埃米尔(Emil)2014年

@Emil-是的,我也等着看。
Optimizer

9

红宝石,正则表达式求解- 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$“ guess”)),如果存在则返回其中的第一个是多种解决方案。

像这样运行

ruby regex-solver.rb cvhjioiugfde

其他任何人都可以随意将这一步骤用作第一个过滤器-我相信它不会抛出任何正确的单词,因此,此初步检查可以大大减少寻找更好算法的搜索空间。

编辑:按照项目建议,我现在选择最长的候选人,这似乎是一个不错的启发。

也感谢es1024提醒我有关重复字母的问题。


做完了 您的日志位于github.com/matsjoyce/codegolf-swipe-type/blob/master/logs /。。。我认为问题在于它是从可能的解决方案中随机选择的,这可以通过选择最长的解决方案来改进。
matsjoyce 2014年

我认为这可能会抛出所有带有两个相同字母的正确单词,例如paradoxically,因为l只会在输入中出现一次,而不是正则表达式要求的两倍。
es1024

@ es1024,嗯,谢谢,当我第一次在沙箱中提出该算法时,我实际上已经意识到这一点,但是昨天却忘记了。稍后将修复。
马丁·恩德

7

C ++,离散Fréchet可距离- 201 220 222 232和232穿过

对我来说,这个问题非常需要弗雷谢特距离,但不幸的是很难计算。

只是为了好玩,我尝试通过实现由Thomas Eiter和Heikki Mannila在《计算离散Fréchet距离》(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也可以正常工作。


1
是! 终于有人用大胖算法名称添加了一个解决方案!你的日志是github.com/matsjoyce/codegolf-swipe-type/blob/master/logs/...
matsjoyce

1
我们宁可称其为解决大型计算机科学问题的简单动态编程算法。
camelNeck

由于某种原因,这似乎无法输入programmijng-它给了我pang
2014年

那很奇怪。我越来越programming喜欢了。您能取消注释该行// cout << thispair->first << ": " << score << endl;并粘贴结果分数吗?
camelNeck

6

Python 2中,周转,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:我在问题上添加了注释,指出了我认为发现的两个拼写错误。:)
Emil

是的,谢谢,我只是给它另一张支票。不过,我不太确定该如何处理不明确的情况。
matsjoyce 2014年

@matsjoyce:可以通过选择键盘上的另一条可能的路径来解决一些歧义。例如,brats可能是'bgrdsasdrtrds'无法与混淆breasts。但是,我也不会介意您是否保持原样。
埃米尔(Emil)

是的,那行得通。我只是担心,如果这套工具过于“理想”,并且最终的评分工具没有经过严格的审查,那么某些解决方案的效果可能会不佳
matsjoyce 2014年

6

PHP,方向判别器,203 213 216 229 231和229个 233遍

这首先通过简单的遍历字典来获得单词列表,这些单词的字母都出现在输入中(Martin在此处所做的事情),并且仅检查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;

@matsjoyce在阅读问题时错过了要点。代码现在使用的位置是keypos.csv
es1024 2014年

@ es1024虽然不是250个测试用例的一部分,但单词列表确实包含238个带有三个连续字母的单词(到目前为止,我检查过的都是以结尾的单词sss)-我认为重复的消除不会抓住这些单词。
马丁·恩德2014年


3

Python 3,Corner Simulator,241和240个传递

算法:

  • 对输入和单词列表中的所有单词进行重复数据删除(删除连续运行的相同字符)(使用字典将原始单词取回)
  • 删除所有未以滑动的开头和结尾开头和结尾的单词(第一遍)
  • 在大于80度的所有角上制作一个正则表达式,然后删除与此不匹配的所有单词(第二遍)
  • 将每个单词正则表达式正则化(例如正则表达式求解器),然后将其拆分为一系列理论上的直线,然后检查它们是否笔直,并且可能是手指沿该行移动产生的(significant_letter功能)(第三遍)
  • 按接近直线的顺序对单词进行排序
  • 然后使用长度作为平局(越长越好)
  • 然后使用Levenshtein距离作为最终的决胜局
  • 输出字!

码:

# 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


这是一个有效的答案。无需让我的答案。
Optimizer

@Optimizer好吧,讨论似乎更喜欢接受您的回答,所以我认为很好。
matsjoyce 2014年

在仅阅读了元讨论之后,我对您的决定表示满意,但这也很好(更好):)
Optimizer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.