Class: MarkovTwitter::MarkovBuilder::Node

Inherits:
Object
  • Object
show all
Defined in:
lib/markov_twitter/markov_builder/node.rb

Overview

Represents a single node in a Markov chain.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value:, nodes:) ⇒ Node

Returns a new instance of Node.

Parameters:

  • value (String)

    for example, a word.

  • nodes (Hash<String,Node>)

    .



28
29
30
31
32
33
# File 'lib/markov_twitter/markov_builder/node.rb', line 28

def initialize(value:, nodes:)
  @value = value
  @linkages = { next: Hash.new(0), prev: Hash.new(0) }
  @total_num_inputs = { next: 0, prev: 0 }
  @nodes = nodes
end

Instance Attribute Details

#linkagesHash<Symbol, Hash<String, Float>>

Returns the :next and :previous linkages.

  • Outer hash is keyed by the direction (:next, :prev).

  • Inner hash represents possible traversals - keyed by string value, its values are probabilities representing the likelihood of choosing that route.

Returns:

  • (Hash<Symbol, Hash<String, Float>>)

    the :next and :previous linkages.

    • Outer hash is keyed by the direction (:next, :prev).

    • Inner hash represents possible traversals - keyed by string value, its values are probabilities representing the likelihood of choosing that route.



15
16
17
# File 'lib/markov_twitter/markov_builder/node.rb', line 15

def linkages
  @linkages
end

#nodesHash<String,Node> (readonly)

Returns a reference to the attr of the parent MarkovBuilder.

Returns:

  • (Hash<String,Node>)

    a reference to the attr of the parent MarkovBuilder.



24
25
26
# File 'lib/markov_twitter/markov_builder/node.rb', line 24

def nodes
  @nodes
end

#total_num_inputsHash<Symbol>, Integer

Returns the total number of inputs added in each direction.

  • also used to re-calculate probabilities.

Returns:

  • (Hash<Symbol>, Integer)

    the total number of inputs added in each direction.

    • also used to re-calculate probabilities.



20
21
22
# File 'lib/markov_twitter/markov_builder/node.rb', line 20

def total_num_inputs
  @total_num_inputs
end

#valueString (readonly)

Returns a single token, such as a word.

Returns:

  • (String)

    a single token, such as a word.



7
8
9
# File 'lib/markov_twitter/markov_builder/node.rb', line 7

def value
  @value
end

Instance Method Details

#add_and_adjust_probabilities(direction, other_node, mirror_change = true) ⇒ void

This method returns an undefined value.

Adds a single node to the :next linkages and updates probabilities. Also updates the opposite direction, e.g. :prev will be updated if something is added to :next.

Parameters:

  • direction (Symbol)

    either :next or :prev.

  • other_node (Node)
  • mirror_change (Boolean) (defaults to: true)

    whether to update the opposite direction, defaults to true.



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/markov_twitter/markov_builder/node.rb', line 43

def add_and_adjust_probabilities(direction, other_node, mirror_change=true)
  @nodes[other_node.value] ||= other_node
  total_num_inputs[direction] += 1
  unit = get_probability_unit(direction)
  probability_multiplier = (total_num_inputs[direction] - 1) * unit
  linkages[direction].each_key do |node_key|
    linkages[direction][node_key] *= probability_multiplier
  end
  linkages[direction][other_node.value] += unit
  # Add a linkage in the opposite direction to keep :next and :prev in sync
  update_opposite_direction(direction, other_node, __method__) if mirror_change
end

#add_linkage!(direction, other_node, probability, mirror_change = true) ⇒ void

This method returns an undefined value.

Force-adds a linkage at a specific probability. Readjusts other probabilities but may break their proportionality. Updates the opposite direction to keep :next and :prev in sync

Parameters:

  • direction (Symbol)
  • other_node (Node)
  • probability (Float)

    between 0 and 1.

  • mirror_change (Boolean) (defaults to: true)

    whether to update the opposite direction.

Raises:

  • (ArgumentError)


127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/markov_twitter/markov_builder/node.rb', line 127

def add_linkage!(direction, other_node, probability, mirror_change=true)
  raise ArgumentError, "invalid probability" unless probability.between?(0,1)
  # first remove any existing node there and distribute the probability.
  delete_linkage!(direction, other_node)
  # Re-adjust each probability to account for the added value
  linkages[direction].each_key do |key|
    linkages[direction][key] *= (1 - probability)
    # remove the linkage if it's probability is zero
    if linkages[direction][key].zero?
      delete_linkage!(direction, @nodes[key])
    end
  end
  # Add the new value and set its probability
  binding.pry if other_node.value == "dog"
  linkages[direction][other_node.value] = probability
  # increment the total count
  total_num_inputs[direction] += 1
  # Add a linkage in the opposite direction to keep :next and :prev in sync      
  if mirror_change
    update_opposite_direction(direction, other_node, __method__, probability)
  end
end

#add_next_linkage(child_node, mirror_change = true) ⇒ void

This method returns an undefined value.

Adds another node to the :next linkages, updating probabilities.

Parameters:

  • child_node (Node)

    to be added.

  • mirror_change (Boolean) (defaults to: true)

    whether to update the opposite direction.



166
167
168
# File 'lib/markov_twitter/markov_builder/node.rb', line 166

def add_next_linkage(child_node, mirror_change=true)
  add_and_adjust_probabilities(:next, child_node)
end

#add_prev_linkage(parent_node, mirror_change = true) ⇒ void

This method returns an undefined value.

Adds another node to the :prev linkages, updating probabilities.

Parameters:

  • parent_node (Node)

    to be added.

  • mirror_change (Boolean) (defaults to: true)

    whether to update the opposite direction.



174
175
176
# File 'lib/markov_twitter/markov_builder/node.rb', line 174

def add_prev_linkage(parent_node, mirror_change=true)
  add_and_adjust_probabilities(:prev, parent_node)
end

#delete_linkage!(direction, other_node, mirror_change = true) ⇒ void

This method returns an undefined value.

Force-removes a linkage, re-adjusting other probabilities but potentially breaking their proportionality. Can be safely run for non-existing nodes. Adjusts the linkages in the opposite direction accordingly.

Parameters:

  • direction (Symbol)
  • other_node (Node)
  • mirror_change (Boolean) (defaults to: true)

    whether to update the opposite direction, defaults to true



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/markov_twitter/markov_builder/node.rb', line 103

def delete_linkage!(direction, other_node, mirror_change=true)
  return unless linkages[direction].has_key? other_node.value
  probability = linkages[direction][other_node.value]
  # delete the linkage
  linkages[direction].delete other_node.value
  # distribute the probability evenly among the other options.
  amt_to_add = probability / linkages[direction].keys.length
  linkages[direction].each_key do |key|
    linkages[direction][key] += amt_to_add
  end
  # decrement the total count
  total_num_inputs[direction] -= 1
  # Add a linkage in the opposite direction to keep :next and :prev in sync      
  update_opposite_direction(direction, other_node, __method__) if mirror_change
end

#get_probability_unit(direction) ⇒ Float

Determines the weight of a single insertion by looking up the total number of insertions in that direction.

Parameters:

  • direction (Symbol)

    :next or :prev

Returns:

  • (Float)

    between 0 and 1.



60
61
62
63
64
65
# File 'lib/markov_twitter/markov_builder/node.rb', line 60

def get_probability_unit(direction)
  unless total_num_inputs[direction] > 0
    raise ArgumentError, "no inputs were added in <direction>"
  end
  1.0 / total_num_inputs[direction]
end

#remove_and_adjust_probabilities(direction, other_node, mirror_change = true) ⇒ void

This method returns an undefined value.

Removes a single node from the :prev linkages and updates probabilities. Safe to run if the other_node is not actually present in the linkages. Mirrors the change in the opposite direction, to keep :prev and :next in sync.

Parameters:

  • direction (Symbol)

    either :next or :prev

  • other_node (Node)

    the node to be removed.

  • mirror_change (Boolean) (defaults to: true)

    whether to update the opposite direction, defaults to true



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/markov_twitter/markov_builder/node.rb', line 75

def remove_and_adjust_probabilities(direction, other_node, mirror_change=true)
  return unless linkages[direction].has_key? other_node.value
  unit = get_probability_unit(direction)
  if linkages[direction][other_node.value] - unit <= 0
    delete_linkage!(direction, other_node)
  else
    linkages[direction][other_node.value] -= unit
    num_per_direction = total_num_inputs[direction]
    linkages[direction].each_key do |node_key|
      linkages[direction][node_key] *= (
        num_per_direction / (num_per_direction - 1.0)
      )
    end
    total_num_inputs[direction] -= 1
  end
  # Add a linkage in the opposite direction to keep :next and :prev in sync
  update_opposite_direction(direction, other_node, __method__) if mirror_change
end

#remove_next_linkage(child_node, mirror_change = true) ⇒ void

This method returns an undefined value.

Removes a node from the :next linkages, updating probabilities.

Parameters:

  • child_node (Node)

    to be removed.

  • mirror_change (Boolean) (defaults to: true)

    whether to update the opposite direction.



182
183
184
# File 'lib/markov_twitter/markov_builder/node.rb', line 182

def remove_next_linkage(child_node, mirror_change=true)
  remove_and_adjust_probabilities(:next, child_node)
end

#remove_prev_linkage(parent_node, mirror_change = true) ⇒ void

This method returns an undefined value.

Removes a node from the :prev linkages, updating probabilities.

Parameters:

  • parent_node (Node)

    to be removed.

  • mirror_change (Boolean) (defaults to: true)

    whether to update the opposite direction.



190
191
192
# File 'lib/markov_twitter/markov_builder/node.rb', line 190

def remove_prev_linkage(parent_node, mirror_change=true)
  remove_and_adjust_probabilities(:prev, parent_node)
end

#update_opposite_direction(direction, other_node, method_name, *other_args) ⇒ void

This method returns an undefined value.

Calls given method_name on other_node, passing the opposite direction to the one given as an argument. This keeps :next and :prev in sync.

Parameters:

  • direction (Symbol)

    something should have already been added/removed here

  • other_node (Node)

    the node which was added/removed

  • method_name (Symbol)

    the method to invoke on other_node



157
158
159
160
# File 'lib/markov_twitter/markov_builder/node.rb', line 157

def update_opposite_direction(direction, other_node, method_name, *other_args)
  other_direction = (%i{next prev} - [direction])[0]
  other_node.send(method_name, other_direction, self, *other_args, false)
end