Class: WeightedListRank::Strategies::Exponential

Inherits:
WeightedListRank::Strategy show all
Defined in:
lib/weighted_list_rank/strategies/exponential.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(exponent: 1.5, bonus_pool_percentage: 1.0, average_list_length: nil, include_unranked_items: false, warn_on_invalid_position: false) ⇒ Exponential

Initializes the Exponential strategy with optional parameters for exponent, bonus pool percentage, average list length, and whether to include unranked items in the bonus pool.

defaults to 1.0 (100%). defaults to nil. defaults to false for backward compatibility. position exceeds the number of items in the list. Such positions are always clamped to the last position; this only controls whether that is reported. Defaults to false, so scoring is silent.

Parameters:

  • exponent (Float) (defaults to: 1.5)

    the exponent to use in the score calculation formula, defaults to 1.5.

  • bonus_pool_percentage (Float) (defaults to: 1.0)

    the percentage of the list's weight to be used as the bonus pool,

  • average_list_length (Float, NilClass) (defaults to: nil)

    the average length of lists in the system, either as a mean or median,

  • include_unranked_items (Boolean) (defaults to: false)

    whether to include unranked items in the bonus pool calculation,

  • warn_on_invalid_position (Boolean) (defaults to: false)

    whether to emit a warning when a list contains items whose



20
21
22
23
24
25
26
27
# File 'lib/weighted_list_rank/strategies/exponential.rb', line 20

def initialize(exponent: 1.5, bonus_pool_percentage: 1.0, average_list_length: nil,
  include_unranked_items: false, warn_on_invalid_position: false)
  @exponent = exponent
  @bonus_pool_percentage = bonus_pool_percentage
  @average_list_length = average_list_length
  @include_unranked_items = include_unranked_items
  @warn_on_invalid_position = warn_on_invalid_position
end

Instance Attribute Details

#average_list_lengthObject (readonly)

Returns the value of attribute average_list_length.



4
5
6
# File 'lib/weighted_list_rank/strategies/exponential.rb', line 4

def average_list_length
  @average_list_length
end

#bonus_pool_percentageObject (readonly)

Returns the value of attribute bonus_pool_percentage.



4
5
6
# File 'lib/weighted_list_rank/strategies/exponential.rb', line 4

def bonus_pool_percentage
  @bonus_pool_percentage
end

#exponentObject (readonly)

Returns the value of attribute exponent.



4
5
6
# File 'lib/weighted_list_rank/strategies/exponential.rb', line 4

def exponent
  @exponent
end

#include_unranked_itemsObject (readonly)

Returns the value of attribute include_unranked_items.



4
5
6
# File 'lib/weighted_list_rank/strategies/exponential.rb', line 4

def include_unranked_items
  @include_unranked_items
end

#warn_on_invalid_positionObject (readonly)

Returns the value of attribute warn_on_invalid_position.



4
5
6
# File 'lib/weighted_list_rank/strategies/exponential.rb', line 4

def warn_on_invalid_position
  @warn_on_invalid_position
end

Instance Method Details

#calculate_score(list, item) ⇒ Float

Calculates the score of a single item within a list based on its rank position, the total number of items, and the list's weight, using an exponential formula. The bonus pool for score adjustments is determined by the specified bonus pool percentage of the list's total weight, adjusted by the average list length.

If include_unranked_items is true, unranked items will also receive a portion of the bonus pool. Ranked items will receive an exponential bonus, while unranked items will split the remaining bonus pool evenly.

Scoring a whole list is cheaper through #calculate_scores, which shares the per-list work across items.

and the bonus pool percentage.

Parameters:

Returns:

  • (Float)

    the calculated score for the item, adjusted by the list's weight, the specified exponent,



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/weighted_list_rank/strategies/exponential.rb', line 90

def calculate_score(list, item)
  items = list.items
  total_items = items.count
  num_ranked_items = count_ranked(items)
  position = item.position

  if position.nil?
    score = list.weight
    if num_ranked_items.zero? && include_unranked_items
      score += adjusted_bonus_pool_for(list, total_items) / total_items
    end
  else
    if position > total_items
      report_invalid_positions(list, 1, total_items)
      position = total_items
    end
    exponential_factor = (total_items + 1 - position)**exponent
    score = list.weight +
      (exponential_factor / exponential_factor_sum(total_items)) * adjusted_bonus_pool_for(list, total_items)
  end

  floor(apply_penalty(score, item.score_penalty))
end

#calculate_scores(list) ⇒ Array<Float>

Calculates the scores for every item in a list in one pass.

The number of ranked items, the adjusted bonus pool, and the total exponential factor are all constant across the list, so they are computed once here instead of once per item. Scoring a list is therefore linear in the number of items.

Parameters:

Returns:

  • (Array<Float>)

    scores positionally matching list.items.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/weighted_list_rank/strategies/exponential.rb', line 38

def calculate_scores(list)
  items = list.items
  total_items = items.count
  return [] if total_items.zero?

  num_ranked_items = count_ranked(items)
  adjusted_bonus_pool = adjusted_bonus_pool_for(list, total_items)

  # Only lists containing ranked items need this, and it is the most
  # expensive value to produce, so it is computed on first use.
  total_exponential_factor = nil
  invalid_positions = 0

  scores = items.map do |item|
    position = item.position

    if position.nil?
      score = list.weight
      if num_ranked_items.zero? && include_unranked_items
        score += adjusted_bonus_pool / total_items
      end
    else
      if position > total_items
        invalid_positions += 1
        position = total_items
      end
      total_exponential_factor ||= exponential_factor_sum(total_items)
      exponential_factor = (total_items + 1 - position)**exponent
      score = list.weight + (exponential_factor / total_exponential_factor) * adjusted_bonus_pool
    end

    floor(apply_penalty(score, item.score_penalty))
  end

  report_invalid_positions(list, invalid_positions, total_items)
  scores
end