Class: Kotoshu::Suggestions::Strategies::BaseStrategy

Inherits:
Object
  • Object
show all
Defined in:
lib/kotoshu/suggestions/strategies/base_strategy.rb

Overview

Base class for suggestion strategies.

Subclasses must implement the #generate method.

Examples:

Implementing a custom strategy

class MyStrategy < BaseStrategy
  def generate(context)
    # Return suggestions based on context.word
    SuggestionSet.from_words(%w[word1 word2], source: :my_strategy)
  end
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name: :base, **config) ⇒ BaseStrategy

Create a new base strategy.

Parameters:

  • name (String, Symbol) (defaults to: :base)

    Strategy name

  • config (Hash)

    Configuration options

Options Hash (**config):

  • max_results (Integer)

    Maximum results to return

  • enabled (Boolean)

    Whether strategy is enabled



30
31
32
33
34
35
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 30

def initialize(name: :base, **config)
  @name = name.to_sym
  @config = config
  @enabled = config.fetch(:enabled, true)
  @max_results = config.fetch(:max_results, 10)
end

Instance Attribute Details

#configHash (readonly)

Returns Strategy configuration.

Returns:

  • (Hash)

    Strategy configuration



22
23
24
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 22

def config
  @config
end

#nameSymbol (readonly)

Returns Strategy name.

Returns:

  • (Symbol)

    Strategy name



19
20
21
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 19

def name
  @name
end

Instance Method Details

#calculate_ngram_similarity(word1, word2) ⇒ Float

Calculate typo correction similarity between two words.

This is a custom similarity metric designed specifically for spelling correction, combining:

  • Character overlap (how many characters are shared)

  • Prefix weight (common prefix is very important for typos)

  • Suffix weight (common ending is also important)

  • Length penalty (very different lengths are less similar)

Returns a value from 0.0 (no similarity) to 1.0 (identical).

Parameters:

  • word1 (String)

    First word

  • word2 (String)

    Second word

Returns:

  • (Float)

    Typo correction similarity (0.0 to 1.0)



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 163

def calculate_ngram_similarity(word1, word2)
  return 0 if word1.nil? || word2.nil? || word1.empty? || word2.empty?

  w1 = word1.downcase
  w2 = word2.downcase

  # Identical strings have maximum similarity
  return 1.0 if w1 == w2

  len1 = w1.length
  len2 = w2.length
  max_len = [len1, len2].max

  # Calculate common prefix length (up to 4 characters)
  prefix_len = 0
  (0...[len1, len2, 4].min).each do |i|
    break if w1[i] != w2[i]
    prefix_len += 1
  end

  # Calculate common suffix length
  suffix_len = 0
  (1..[len1, len2, 4].min).each do |i|
    break if w1[-i] != w2[-i]
    suffix_len += 1
  end

  # Calculate character overlap (how many characters from w1 are in w2)
  w2_chars = w2.chars
  overlap = w1.chars.count { |c| w2_chars.include?(c) }

  # Calculate similarity score
  # 1. Base score from character overlap
  similarity = overlap.to_f / max_len

  # 2. Prefix bonus (common start is very important for typos)
  prefix_bonus = prefix_len * 0.15

  # 3. Suffix bonus (common ending is also important)
  suffix_bonus = suffix_len * 0.05

  # 4. Length penalty (very different lengths are less similar)
  length_diff = (len1 - len2).abs
  length_penalty = length_diff * 0.1

  # Combine all factors
  similarity = similarity + prefix_bonus + suffix_bonus - length_penalty

  # Cap at 1.0, floor at 0.0
  [[similarity, 1.0].min, 0.0].max
end

#create_suggestion(word, distance: 0, confidence: 1.0, **metadata) ⇒ Suggestion

Create a suggestion from a word.

Parameters:

  • word (String)

    The suggested word

  • distance (Integer) (defaults to: 0)

    Edit distance

  • confidence (Float) (defaults to: 1.0)

    Confidence score

  • metadata (Hash)

    Additional metadata for ranking

Returns:



106
107
108
109
110
111
112
113
114
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 106

def create_suggestion(word, distance: 0, confidence: 1.0, **)
  Suggestion.new(
    word: word,
    distance: distance,
    confidence: confidence,
    source: @name,
    **
  )
end

#create_suggestion_set(words, distances: {}, original_word: nil) ⇒ SuggestionSet

Create a suggestion set from words.

Parameters:

  • words (Array<String>)

    Array of words

  • distances (Hash) (defaults to: {})

    Optional word => distance mapping

  • original_word (String) (defaults to: nil)

    The original misspelled word (for ranking)

Returns:



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 122

def create_suggestion_set(words, distances: {}, original_word: nil)
  suggestions = words.map do |word|
    # Try case-sensitive first, then case-insensitive for distance lookup
    distance = if distances.key?(word)
                distances[word]
              else
                distances.fetch(word.downcase, 1)
              end
    confidence = calculate_confidence(distance)

    # Calculate n-gram similarity (like Hunspell) for better ranking
    ngram_score = if original_word
                    calculate_ngram_similarity(original_word, word)
                  else
                    0
                  end

     = {
      original_length: original_word&.length || word.length,
      ngram_score: ngram_score
    }

    create_suggestion(word, distance: distance, confidence: confidence, **)
  end
  SuggestionSet.new(suggestions, max_size: max_results)
end

#enabled?Boolean

Check if this strategy is enabled.

Returns:

  • (Boolean)

    True if enabled



50
51
52
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 50

def enabled?
  @enabled
end

#generate(context) ⇒ SuggestionSet

This method is abstract.

Subclasses must implement this method.

Generate suggestions for a word.

Parameters:

  • context (Context)

    The suggestion context

Returns:

Raises:

  • (NotImplementedError)

    Subclass must implement



43
44
45
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 43

def generate(context)
  raise NotImplementedError, "#{self.class} must implement #generate"
end

#generate_ngrams(word, n) ⇒ Set<String>

Generate n-grams for a word.

Parameters:

  • word (String)

    The word

  • n (Integer)

    N-gram size

Returns:

  • (Set<String>)

    Set of n-grams



220
221
222
223
224
225
226
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 220

def generate_ngrams(word, n)
  ngrams = Set.new
  (word.length - n + 1).times do |i|
    ngrams.add(word[i, n])
  end
  ngrams
end

#get_config(key, default = nil) ⇒ Object

Get a configuration value.

Parameters:

  • key (Symbol)

    The config key

  • default (Object) (defaults to: nil)

    Default value if not set

Returns:

  • (Object)

    The config value



67
68
69
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 67

def get_config(key, default = nil)
  @config.fetch(key, default)
end

#handles?(context) ⇒ Boolean

Check if this strategy should handle the context.

Default implementation checks if the word is not in the dictionary. Subclasses can override for more specific logic.

Parameters:

  • context (Context)

    The suggestion context

Returns:

  • (Boolean)

    True if the strategy should handle this context



93
94
95
96
97
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 93

def handles?(context)
  return false unless enabled?

  !dictionary_lookup(context, context.word)
end

#has_config?(key) ⇒ Boolean

Check if a config value is present.

Parameters:

  • key (Symbol)

    The config key

Returns:

  • (Boolean)

    True if config has the key



75
76
77
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 75

def has_config?(key)
  @config.key?(key)
end

#max_results(default = 10) ⇒ Integer

Get the max results configuration.

Parameters:

  • default (Integer) (defaults to: 10)

    Default value if not set

Returns:

  • (Integer)

    Max results



58
59
60
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 58

def max_results(default = 10)
  @max_results || default
end

#priorityInteger

Get the priority for this strategy.

Returns:

  • (Integer)

    Priority (lower = higher priority)



82
83
84
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 82

def priority
  @config.fetch(:priority, 100)
end

#to_sString Also known as: inspect

Convert strategy to string.

Returns:

  • (String)

    String representation



231
232
233
# File 'lib/kotoshu/suggestions/strategies/base_strategy.rb', line 231

def to_s
  "#{self.class.name}(name: #{@name}, enabled: #{enabled?})"
end