patternpythonMinor
Algorithm for detecting keyboard slips
Viewed 0 times
algorithmslipsforkeyboarddetecting
Problem
I decided to make a script that will evaluate a user input and attempt to figure out what keyword they meant to type, by checking what characters are next to the ones they entered.
ie. On a QWERTY keyboard,
It starts by breaking the list of keywords into a 2D array of characters at each position. So the first list contains letters that occur at the start of keywords, then the next list contains letters that occur second in keywords etc.
I worked it this way to reduce the amount of work the script does when checking potentially intended inputs against keywords. And if I wanted to rewrite it as a class, I could have the indexed keywords calculated once and stored. That would mean that it only needs to evaluate the user inputs against the stored information on keyword characters.
I also defined a subclass of list in order to have it treat negative indices as errors that I could catch and pass as well as to not allow those lists to have empty variables appended as both of those behaved better for my purposes.
```
class StrictList(list):
"""List subclass, rejects appending falsey items and negative indexing.
Extends __getitem__ to raise IndexErrors with negative indices.
Extends __append__ to ignore append arguments that are falsey.
"""
def __getitem__(self, n):
if n < 0:
raise IndexError("Strict lists don't accept negative indexing.")
return list.__getitem__(self, n)
def append(self, n):
if not n:
return
return list.append(self, n)
def check(c):
"""Return only valid characters."""
return c if c in validChars else ''
def neighbour_keys(k):
"""Return a list of keys physically near character 'k' on the keyboard."""
k = k.lower()
keys = StrictList()
keys.append(check(k))
index = []
for i,row in enumerate(keyboard):
ie. On a QWERTY keyboard,
d is surrounded by e,r,s,f,x and c, so if you wrote selete, this script would be able to check if you actually meant delete. It starts by breaking the list of keywords into a 2D array of characters at each position. So the first list contains letters that occur at the start of keywords, then the next list contains letters that occur second in keywords etc.
I worked it this way to reduce the amount of work the script does when checking potentially intended inputs against keywords. And if I wanted to rewrite it as a class, I could have the indexed keywords calculated once and stored. That would mean that it only needs to evaluate the user inputs against the stored information on keyword characters.
I also defined a subclass of list in order to have it treat negative indices as errors that I could catch and pass as well as to not allow those lists to have empty variables appended as both of those behaved better for my purposes.
```
class StrictList(list):
"""List subclass, rejects appending falsey items and negative indexing.
Extends __getitem__ to raise IndexErrors with negative indices.
Extends __append__ to ignore append arguments that are falsey.
"""
def __getitem__(self, n):
if n < 0:
raise IndexError("Strict lists don't accept negative indexing.")
return list.__getitem__(self, n)
def append(self, n):
if not n:
return
return list.append(self, n)
def check(c):
"""Return only valid characters."""
return c if c in validChars else ''
def neighbour_keys(k):
"""Return a list of keys physically near character 'k' on the keyboard."""
k = k.lower()
keys = StrictList()
keys.append(check(k))
index = []
for i,row in enumerate(keyboard):
Solution
I disapprove of
You say that you wrote it because it "behaved better for my purposes" but this doesn't actually explain anything. As far as I can tell, you use the features of this class in two places:
-
In
and here's the same code with explicit bounds checks:
which is one line shorter.
-
In
Instead of:
why not write:
and get rid of
add
strictList. Writing a special subclass of list seems complex and error-prone. Why override append but not __setitem__? Why does __getitem__ not handle slices? The implementation seems to be tailored so closely to the use case that it does not seem likely that this class could have any other uses.You say that you wrote it because it "behaved better for my purposes" but this doesn't actually explain anything. As far as I can tell, you use the features of this class in two places:
-
In
neighbourKeys you want to make a list of neighbouring keys, and you want to get an IndexError if any of the neighbours go out of bounds. But in fact this doesn't make the code any shorter. Here's the original code:try:
neighbourKey = keyboard[index[0] + val[0]][index[1] + val[1]]
keys.append(check(neighbourKey))
except IndexError:
passand here's the same code with explicit bounds checks:
row, col = index[0] + val[0], index[1] + val[1]
if 0 <= row < len(keyboard) and 0 <= col < len(keyboard[row]):
neighbourKey = keyboard[row][col]
keys.append(check(neighbourKey))which is one line shorter.
-
In
neighbourKeys you want to make sure you only add valid keys to the list, and so you always call keys.append(check(k)), and you know that check(k) returns an empty string if k is invalid, and you know that keys.append will ignore an empty string. But this is very complex: there are a lot of things that you have to understand in order to figure out how this code works, and these things are spread out in different functions.Instead of:
keys.append(check(neighbourKey))why not write:
if neighbourKey in validChars:
keys.append(neighbourKey)and get rid of
check and strictList altogether? At the same time, instead of the special case for k:keys.append(check(k))add
(0, 0) to the list of offsets so that this case is no longer special.Code Snippets
try:
neighbourKey = keyboard[index[0] + val[0]][index[1] + val[1]]
keys.append(check(neighbourKey))
except IndexError:
passrow, col = index[0] + val[0], index[1] + val[1]
if 0 <= row < len(keyboard) and 0 <= col < len(keyboard[row]):
neighbourKey = keyboard[row][col]
keys.append(check(neighbourKey))keys.append(check(neighbourKey))if neighbourKey in validChars:
keys.append(neighbourKey)keys.append(check(k))Context
StackExchange Code Review Q#92986, answer score: 8
Revisions (0)
No revisions yet.