did_you_mean 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +48 -0
- data/CHANGELOG.md +75 -75
- data/Gemfile +2 -1
- data/README.md +2 -32
- data/Rakefile +4 -5
- data/appveyor.yml +25 -0
- data/did_you_mean.gemspec +6 -4
- data/documentation/CHANGELOG.md.erb +8 -0
- data/documentation/changelog_generator.rb +34 -0
- data/documentation/human_typo_api.md +20 -0
- data/documentation/tree_spell_algorithm.md +82 -0
- data/documentation/tree_spell_checker_api.md +24 -0
- data/lib/did_you_mean.rb +17 -16
- data/lib/did_you_mean/experimental.rb +2 -2
- data/lib/did_you_mean/experimental/initializer_name_correction.rb +1 -1
- data/lib/did_you_mean/experimental/ivar_name_correction.rb +3 -1
- data/lib/did_you_mean/levenshtein.rb +1 -1
- data/lib/did_you_mean/spell_checker.rb +7 -7
- data/lib/did_you_mean/spell_checkers/key_error_checker.rb +8 -2
- data/lib/did_you_mean/spell_checkers/method_name_checker.rb +14 -6
- data/lib/did_you_mean/spell_checkers/name_error_checkers.rb +2 -2
- data/lib/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb +5 -5
- data/lib/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb +1 -1
- data/lib/did_you_mean/tree_spell_checker.rb +137 -0
- data/lib/did_you_mean/verbose.rb +2 -2
- data/lib/did_you_mean/version.rb +1 -1
- data/test/core_ext/test_name_error_extension.rb +48 -0
- data/test/edit_distance/{jaro_winkler_test.rb → test_jaro_winkler.rb} +2 -2
- data/test/fixtures/mini_dir.yml +15 -0
- data/test/fixtures/rspec_dir.yml +112 -0
- data/test/helper.rb +29 -0
- data/test/spell_checking/{class_name_check_test.rb → test_class_name_check.rb} +12 -10
- data/test/spell_checking/{key_name_check_test.rb → test_key_name_check.rb} +18 -8
- data/test/spell_checking/{method_name_check_test.rb → test_method_name_check.rb} +17 -15
- data/test/spell_checking/{uncorrectable_name_check_test.rb → test_uncorrectable_name_check.rb} +3 -3
- data/test/spell_checking/{variable_name_check_test.rb → test_variable_name_check.rb} +18 -16
- data/test/{spell_checker_test.rb → test_spell_checker.rb} +2 -2
- data/test/test_tree_spell_checker.rb +173 -0
- data/test/test_verbose_formatter.rb +21 -0
- data/test/tree_spell/change_word.rb +61 -0
- data/test/tree_spell/human_typo.rb +89 -0
- data/test/tree_spell/test_change_word.rb +38 -0
- data/test/tree_spell/test_explore.rb +128 -0
- data/test/tree_spell/test_human_typo.rb +24 -0
- metadata +47 -58
- data/.travis.yml +0 -23
- data/test/core_ext/name_error_extension_test.rb +0 -51
- data/test/experimental/initializer_name_correction_test.rb +0 -15
- data/test/experimental/method_name_checker_test.rb +0 -13
- data/test/test_helper.rb +0 -13
- data/test/verbose_formatter_test.rb +0 -22
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# did_you_mean [![Gem Version](https://badge.fury.io/rb/did_you_mean.svg)](https://rubygems.org/gems/did_you_mean) [![Build Status](https://travis-ci.org/
|
1
|
+
# did_you_mean [![Gem Version](https://badge.fury.io/rb/did_you_mean.svg)](https://rubygems.org/gems/did_you_mean) [![Build Status](https://travis-ci.org/ruby/did_you_mean.svg?branch=master)](https://travis-ci.org/ruby/did_you_mean)
|
2
2
|
|
3
3
|
## Installation
|
4
4
|
|
@@ -61,36 +61,6 @@ hash.fetch(:fooo)
|
|
61
61
|
# Did you mean? :foo
|
62
62
|
```
|
63
63
|
|
64
|
-
## Experimental Features
|
65
|
-
|
66
|
-
Aside from the basic features above, the `did_you_mean` gem comes with experimental features. They can be enabled by calling `require 'did_you_mean/experimental'`.
|
67
|
-
|
68
|
-
Note that **these experimental features should never be enabled in production as they would impact Ruby's performance and use some unstable Ruby APIs.**
|
69
|
-
|
70
|
-
### Correcting an Instance Variable When It's Incorrectly Spelled
|
71
|
-
|
72
|
-
```ruby
|
73
|
-
require 'did_you_mean/experimental'
|
74
|
-
|
75
|
-
@full_name = "Yuki Nishijima"
|
76
|
-
@full_anme.split(" ")
|
77
|
-
# => NoMethodError: undefined method `split' for nil:NilClass
|
78
|
-
# Did you mean? @full_name
|
79
|
-
```
|
80
|
-
|
81
|
-
### Displaying a Warning When `initialize` is Incorrectly Spelled
|
82
|
-
|
83
|
-
```ruby
|
84
|
-
require 'did_you_mean/experimental'
|
85
|
-
|
86
|
-
class Person
|
87
|
-
def intialize
|
88
|
-
...
|
89
|
-
end
|
90
|
-
end
|
91
|
-
# => warning: intialize might be misspelled, perhaps you meant initialize?
|
92
|
-
```
|
93
|
-
|
94
64
|
## Verbose Formatter
|
95
65
|
|
96
66
|
This verbose formatter changes the error message format to take more lines/spaces so it'll be slightly easier to read the suggestions. This formatter can totally be used in any environment including production.
|
@@ -183,7 +153,7 @@ bundle exec benchmark-driver benchmark/speed.yml --rbenv '2.6.0 --jit;2.6.0;2.5.
|
|
183
153
|
|
184
154
|
## Contributing
|
185
155
|
|
186
|
-
1. Fork it (
|
156
|
+
1. Fork it (https://github.com/ruby/did_you_mean/fork)
|
187
157
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
188
158
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
189
159
|
4. Make sure all tests pass (`bundle exec rake`)
|
data/Rakefile
CHANGED
@@ -4,20 +4,19 @@ require 'rake/testtask'
|
|
4
4
|
Rake::TestTask.new do |task|
|
5
5
|
task.libs << "test"
|
6
6
|
|
7
|
-
task.test_files = Dir['test
|
7
|
+
task.test_files = Dir['test/**/test_*.rb'].reject {|path| path.end_with?("test_explore.rb") }
|
8
8
|
task.verbose = true
|
9
9
|
task.warning = true
|
10
10
|
end
|
11
11
|
|
12
|
-
Rake::TestTask.new("test:
|
12
|
+
Rake::TestTask.new("test:explore") do |task|
|
13
13
|
task.libs << "test"
|
14
|
-
task.pattern = 'test/
|
14
|
+
task.pattern = 'test/tree_spell/test_explore.rb'
|
15
15
|
task.verbose = true
|
16
16
|
task.warning = true
|
17
|
-
task.ruby_opts << "-rdid_you_mean/experimental"
|
18
17
|
end
|
19
18
|
|
20
|
-
task default: %i(test
|
19
|
+
task default: %i(test)
|
21
20
|
|
22
21
|
namespace :test do
|
23
22
|
namespace :accuracy do
|
data/appveyor.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
install:
|
2
|
+
- set PATH=C:\Ruby26-x64\bin;%PATH%
|
3
|
+
|
4
|
+
build: off
|
5
|
+
|
6
|
+
branches:
|
7
|
+
only:
|
8
|
+
- master
|
9
|
+
|
10
|
+
environment:
|
11
|
+
ruby_version: "24-%Platform%"
|
12
|
+
zlib_version: "1.2.11"
|
13
|
+
matrix:
|
14
|
+
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
15
|
+
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
16
|
+
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
17
|
+
|
18
|
+
before_test:
|
19
|
+
- ruby -v
|
20
|
+
- gem -v
|
21
|
+
- bundle -v
|
22
|
+
- gem uni did_you_mean
|
23
|
+
|
24
|
+
test_script:
|
25
|
+
- rake
|
data/did_you_mean.gemspec
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
|
4
|
+
begin
|
5
|
+
require_relative "lib/did_you_mean/version"
|
6
|
+
rescue LoadError # Fallback to load version file in ruby core repository
|
7
|
+
require_relative "version"
|
8
|
+
end
|
5
9
|
|
6
10
|
Gem::Specification.new do |spec|
|
7
11
|
spec.name = "did_you_mean"
|
@@ -10,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
10
14
|
spec.email = ["mail@yukinishijima.net"]
|
11
15
|
spec.summary = '"Did you mean?" experience in Ruby'
|
12
16
|
spec.description = 'The gem that has been saving people from typos since 2014.'
|
13
|
-
spec.homepage = "https://github.com/
|
17
|
+
spec.homepage = "https://github.com/ruby/did_you_mean"
|
14
18
|
spec.license = "MIT"
|
15
19
|
|
16
20
|
spec.files = `git ls-files`.split($/).reject{|path| path.start_with?('evaluation/') }
|
@@ -19,7 +23,5 @@ Gem::Specification.new do |spec|
|
|
19
23
|
|
20
24
|
spec.required_ruby_version = '>= 2.5.0'
|
21
25
|
|
22
|
-
spec.add_development_dependency "bundler"
|
23
26
|
spec.add_development_dependency "rake"
|
24
|
-
spec.add_development_dependency "minitest"
|
25
27
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
require 'reverse_markdown'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
class ChangeLogGenerator
|
6
|
+
attr :repository, :template_path, :changelog_path
|
7
|
+
|
8
|
+
def initialize(repository, template_path: "CHANGELOG.md.erb", changelog_path: "CHANGELOG.md")
|
9
|
+
@repository = repository
|
10
|
+
@template_path = template_path
|
11
|
+
@changelog_path = changelog_path
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate_and_save!
|
15
|
+
changelog_in_md = ERB.new(template).result(binding)
|
16
|
+
changelog_in_html = Octokit.markdown(changelog_in_md, context: repository, mode: "gfm")
|
17
|
+
|
18
|
+
File.open(changelog_path, 'w') do |file|
|
19
|
+
file.write ReverseMarkdown.convert(changelog_in_html, github_flavored: true)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def template
|
26
|
+
open("#{__dir__}/#{template_path}").read
|
27
|
+
end
|
28
|
+
|
29
|
+
def releases
|
30
|
+
@releases ||= Octokit.releases(repository)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
ChangeLogGenerator.new("ruby/did_you_mean").generate_and_save!
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# HumanTypo API
|
2
|
+
## Description
|
3
|
+
Simulate an error prone human typist. Assumes typographical errors are Poisson distributed and
|
4
|
+
each error is either a deletion, insertion, substitution, or transposition
|
5
|
+
## Initialization
|
6
|
+
```
|
7
|
+
def initialize(input, lambda: 0.05)
|
8
|
+
end
|
9
|
+
```
|
10
|
+
where
|
11
|
+
### input: A string with the word to be corrupted.
|
12
|
+
## lambda: Error rate of the poisson process
|
13
|
+
The default of 0.05 corresponds to one error every 20 characters, and is thought to approximate the average, competent typist
|
14
|
+
## Methods
|
15
|
+
```
|
16
|
+
def call
|
17
|
+
end
|
18
|
+
```
|
19
|
+
Returns a word with typographical errors.
|
20
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# TreeSpellChecker Algorithm
|
2
|
+
## Overview
|
3
|
+
The algorithm is designed to work on a dictionary that has a rooted tree structure.
|
4
|
+
|
5
|
+
The algorithm treats the problem as a hidden state system, which tries to identify the true state of the input. Due to typographical errors, the state of the input is a hidden version of the true system state. Each word in the dictionary is mapped to a multi-dimensional state, with the first dimension being being the root, the second dimension being the next branch, and so on. Each dimension is discrete with a finite number of elements. The first dimension corresponds to the root, so only has one element.
|
6
|
+
|
7
|
+
The algorithm assumes the input state has the correct structure, and so generates the state of the input. It starts with the root of the input word and maps it to the root element. It then looks at the value of second dimension of the input word and chooses closest elements of the possible second dimension elements. It then continues to the third and higher dimensions. It terminates when it has worked out possible elements corresponding to the highest dimension of the input word. At this point it has the possible elements at for each dimension of the input word. It then generates all possible legitimate states from these elements. Finally it then compares the possible leaves at the end of these legitimate states with the leaf of the input word. From this process it produces suggested states.
|
8
|
+
|
9
|
+
## Accuracy
|
10
|
+
The accuracy of the algorithm was tested using the HumanTypo class. It simulates a human typist by assuming that errors are Poisson distributed at a rate of one typo per 20 characters. Typos can be either a deletion, an insertion, substitution or a transposition.
|
11
|
+
|
12
|
+
I ran 10,000 repititions on both the `test` directory of the ```did_you_mean``` gem and on the ```spec``` directory of the ```rspec-core``` gem.
|
13
|
+
|
14
|
+
The results were as follows:
|
15
|
+
```
|
16
|
+
Minitest Summary
|
17
|
+
--------------------------------------------------------------------------------
|
18
|
+
Method | First Time (%) Mean Suggestions Failures (%)
|
19
|
+
--------------------------------------------------------------------------------
|
20
|
+
Tree 98.0 1.1 2.0
|
21
|
+
Standard 98.1 2.2 1.6
|
22
|
+
Augmented 100.0 1.1 0.0
|
23
|
+
```
|
24
|
+
and
|
25
|
+
```
|
26
|
+
Rspec Summary
|
27
|
+
--------------------------------------------------------------------------------
|
28
|
+
Method | First Time (%) Mean Suggestions Failures (%)
|
29
|
+
--------------------------------------------------------------------------------
|
30
|
+
Tree 94.7 1.0 5.3
|
31
|
+
Standard 98.2 4.2 1.1
|
32
|
+
Augmented 99.7 1.2 0.2
|
33
|
+
```
|
34
|
+
As well, I checked the results on the ```test``` directory with ```HumanTypo``` generating errors at three times the rate:
|
35
|
+
```
|
36
|
+
Minitest Summary (lambda = 0.15)
|
37
|
+
--------------------------------------------------------------------------------
|
38
|
+
Method | First Time (%) Mean Suggestions Failures (%)
|
39
|
+
--------------------------------------------------------------------------------
|
40
|
+
Tree 88.9 1.0 11.0
|
41
|
+
Standard 95.0 1.4 4.3
|
42
|
+
Combined 99.0 1.0 0.8
|
43
|
+
```
|
44
|
+
In all cases, the tree speller, when augmented by the standard spell checker performed with higher accuracy, and giving far fewer suggestions.
|
45
|
+
|
46
|
+
## Execution Speed
|
47
|
+
|
48
|
+
I tested the execution time on the ```test ``` directory:
|
49
|
+
```
|
50
|
+
Testing execution time of Standard
|
51
|
+
Average time (ms): 5.2
|
52
|
+
|
53
|
+
Testing execution time of Tree
|
54
|
+
Average time (ms): 1.1
|
55
|
+
|
56
|
+
Testing execution time of Augmented Tree
|
57
|
+
Average time (ms): 1.2
|
58
|
+
```
|
59
|
+
and on the ```spec``` directory
|
60
|
+
```
|
61
|
+
|
62
|
+
Testing execution time of Standard
|
63
|
+
Average time (ms): 40.6
|
64
|
+
|
65
|
+
Testing execution time of Tree
|
66
|
+
Average time (ms): 2.7
|
67
|
+
|
68
|
+
Testing execution time of Augmented Tree
|
69
|
+
Average time (ms): 4.5
|
70
|
+
```
|
71
|
+
|
72
|
+
I was surprised by how much faster the tree checker was compared to the standard checker. I think the reason is that the predominant computational load will scale with O(log n) where n is the total number of words in the dictionary. My reasoning is that the algorithm very quickly prunes out states as it moves through the dimensions.
|
73
|
+
## Augmentation option
|
74
|
+
Given the major difference in speed between the standard and tree checker, and the likelihood that the disparity will grow rapidly with the size of the dictionary, then I suspect for some applications, it will not be not practicable to augment the tree checker by using the standard checker when the tree checker fails to find a suggestion. Accordingly, I have added an option, ```:augment?```. The default is nil, but if true, then the standard checker is used if there are no suggestions.
|
75
|
+
## Generation of Performance data
|
76
|
+
This is done using ```test/tree_spell/explore_test.rb```. This is not a proper test file in that there are no assertions in it. As well, it takes over ten minutes to run, accordingly, I have disabled it by setting the constant TREE_SPELL_EXPLORE to false at the top of the file. To run the file, set TREE_SPELL_EXPLORE to true. It is also possible to run quick assessments by using a smaller value of n_repeat in the various tests.
|
77
|
+
## Future Work
|
78
|
+
I have identified two categories of remaining errors. The first class is when one of the elements is corrupted to being very small. Then the standard checker does not suggest the correct element, e.g. if an element is ```core``` and it is reduced to ```co```, the standard speller will not make a suggestion. The second class of error is when the structure of the word is broken because one of the separators has been removed. I think it might be possible to remove the first type of error and dramatically reduce the second type of error in a future version. This would be done as follows:
|
79
|
+
- At each level, choose the dictionary element with the smallest distance.
|
80
|
+
- This will work well unless the structure is broken, in which case it could return a wildly wrong suggestion.
|
81
|
+
- To guard against this, the suggestion could be checked against the input word using the standard checker. If the standard checker rejects the suggestion, then it is assumed the structure is broken.
|
82
|
+
- A large proportion of the time, a broken structure will be just due to one separator missing, and the order of the elements will not be affected. Accordingly, the structure can be fixed by comparing the input elements with a concatenation of two levels of the dictionary elements. It would be possible to use the same idea to fix more than one separator missing, but this could quickly become computationally expensive.
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# TreeSpellChecker API
|
2
|
+
## Description
|
3
|
+
|
4
|
+
## Initialization
|
5
|
+
```
|
6
|
+
def initialize(dictionary:, separator: '/', augment: nil)
|
7
|
+
end
|
8
|
+
```
|
9
|
+
where
|
10
|
+
####dictionary: The dictionary is a list of possible words
|
11
|
+
* that are used to correct a misspelling
|
12
|
+
* The dictionary must be tree structured with a single character separator
|
13
|
+
* e.g 'spec/models/goals_spec_rb'.
|
14
|
+
####separator: A single charactor. Cannot be cannot be alphabetical, '@' or '.'.
|
15
|
+
####augment: When set to true, the checker will used the standard ```SpellChecker``` to find possible suggestions.
|
16
|
+
## Methods
|
17
|
+
```
|
18
|
+
def correct(input)
|
19
|
+
end
|
20
|
+
```
|
21
|
+
where
|
22
|
+
####input: Is the input word to be corrected.
|
23
|
+
|
24
|
+
|
data/lib/did_you_mean.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require_relative "did_you_mean/version"
|
2
|
+
require_relative "did_you_mean/core_ext/name_error"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
require_relative "did_you_mean/spell_checker"
|
5
|
+
require_relative 'did_you_mean/spell_checkers/name_error_checkers'
|
6
|
+
require_relative 'did_you_mean/spell_checkers/method_name_checker'
|
7
|
+
require_relative 'did_you_mean/spell_checkers/key_error_checker'
|
8
|
+
require_relative 'did_you_mean/spell_checkers/null_checker'
|
9
|
+
require_relative 'did_you_mean/formatters/plain_formatter'
|
10
|
+
require_relative 'did_you_mean/tree_spell_checker'
|
11
11
|
|
12
12
|
# The +DidYouMean+ gem adds functionality to suggest possible method/class
|
13
13
|
# names upon errors such as +NameError+ and +NoMethodError+. In Ruby 2.3 or
|
@@ -86,14 +86,15 @@ module DidYouMean
|
|
86
86
|
# Map of error types and spell checker objects.
|
87
87
|
SPELL_CHECKERS = Hash.new(NullChecker)
|
88
88
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
89
|
+
# Adds +DidYouMean+ functionality to an error using a given spell checker
|
90
|
+
def self.correct_error(error_class, spell_checker)
|
91
|
+
SPELL_CHECKERS[error_class.name] = spell_checker
|
92
|
+
error_class.prepend(Correctable) unless error_class < Correctable
|
93
|
+
end
|
94
94
|
|
95
|
-
NameError
|
96
|
-
KeyError
|
95
|
+
correct_error NameError, NameErrorCheckers
|
96
|
+
correct_error KeyError, KeyErrorChecker
|
97
|
+
correct_error NoMethodError, MethodNameChecker
|
97
98
|
|
98
99
|
# Returns the currenctly set formatter. By default, it is set to +DidYouMean::Formatter+.
|
99
100
|
def self.formatter
|
@@ -1,2 +1,2 @@
|
|
1
|
-
|
2
|
-
require
|
1
|
+
warn "Experimental features in the did_you_mean gem has been removed " \
|
2
|
+
"and `require \"did_you_mean/experimental\"' has no effect."
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require_relative '../../did_you_mean'
|
4
4
|
|
5
5
|
module DidYouMean
|
6
6
|
module Experimental #:nodoc:
|
@@ -36,6 +36,8 @@ module DidYouMean
|
|
36
36
|
|
37
37
|
@location = no_method_error.backtrace_locations.first
|
38
38
|
@ivar_names = no_method_error.frame_binding.receiver.instance_variables
|
39
|
+
|
40
|
+
no_method_error.remove_instance_variable(:@frame_binding)
|
39
41
|
end
|
40
42
|
|
41
43
|
def corrections
|
@@ -41,7 +41,7 @@ module DidYouMean
|
|
41
41
|
|
42
42
|
# detects the minimum value out of three arguments. This method is
|
43
43
|
# faster than `[a, b, c].min` and puts less GC pressure.
|
44
|
-
# See https://github.com/
|
44
|
+
# See https://github.com/ruby/did_you_mean/pull/1 for a performance
|
45
45
|
# benchmark.
|
46
46
|
def min3(a, b, c)
|
47
47
|
if a < b && a < c
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
require_relative "levenshtein"
|
4
|
+
require_relative "jaro_winkler"
|
5
5
|
|
6
6
|
module DidYouMean
|
7
7
|
class SpellChecker
|
8
|
-
def initialize(dictionary:
|
8
|
+
def initialize(dictionary:)
|
9
9
|
@dictionary = dictionary
|
10
10
|
end
|
11
11
|
|
@@ -13,14 +13,14 @@ module DidYouMean
|
|
13
13
|
input = normalize(input)
|
14
14
|
threshold = input.length > 3 ? 0.834 : 0.77
|
15
15
|
|
16
|
-
words = @dictionary.select {|word| JaroWinkler.distance(normalize(word), input) >= threshold }
|
17
|
-
words.reject! {|word| input == word.to_s }
|
18
|
-
words.sort_by! {|word| JaroWinkler.distance(word.to_s, input) }
|
16
|
+
words = @dictionary.select { |word| JaroWinkler.distance(normalize(word), input) >= threshold }
|
17
|
+
words.reject! { |word| input == word.to_s }
|
18
|
+
words.sort_by! { |word| JaroWinkler.distance(word.to_s, input) }
|
19
19
|
words.reverse!
|
20
20
|
|
21
21
|
# Correct mistypes
|
22
22
|
threshold = (input.length * 0.25).ceil
|
23
|
-
corrections = words.select {|c| Levenshtein.distance(normalize(c), input) <= threshold }
|
23
|
+
corrections = words.select { |c| Levenshtein.distance(normalize(c), input) <= threshold }
|
24
24
|
|
25
25
|
# Correct misspells
|
26
26
|
if corrections.empty?
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative "../spell_checker"
|
2
2
|
|
3
3
|
module DidYouMean
|
4
4
|
class KeyErrorChecker
|
@@ -8,7 +8,13 @@ module DidYouMean
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def corrections
|
11
|
-
@corrections ||= SpellChecker.new(dictionary: @keys).correct(@key).map(&:inspect)
|
11
|
+
@corrections ||= exact_matches.empty? ? SpellChecker.new(dictionary: @keys).correct(@key).map(&:inspect) : exact_matches
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def exact_matches
|
17
|
+
@exact_matches ||= @keys.select { |word| @key == word.to_s }.map(&:inspect)
|
12
18
|
end
|
13
19
|
end
|
14
20
|
end
|