Skip to content

Commit

Permalink
Add duplicate resolution to matching
Browse files Browse the repository at this point in the history
  • Loading branch information
JacobEvelyn committed Jun 1, 2015
1 parent 9d17ec3 commit dd96dba
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 41 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
mkmf.log
*.gem
ideas.txt
TODO.txt
26 changes: 16 additions & 10 deletions lib/friends/activity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,33 +51,39 @@ def serialize
# Modify the description to turn inputted friend names
# (e.g. "Jacob" or "Jacob Evelyn") into full asterisk'd names
# (e.g. "**Jacob Evelyn**")
# @param introvert [Introvert] for use in aggregate computations
# @param friends [Array] list of friends to highlight in the description
# @raise [FriendsError] if more than one friend matches a part of the
# description
def highlight_friends(friends:)
def highlight_friends(introvert:, friends:)
# Map each friend to a list of all possible regexes for that friend.
friend_regexes = {}
friends.each { |f| friend_regexes[f.name] = regexes_for_name(f.name) }
friends.each { |f| friend_regexes[f] = regexes_for_name(f.name) }

# Create hash mapping regex to friend name. Note that because two friends
# may have the same regex (e.g. /John/), we need to store the names in an
# Create hash mapping regex to friend. Note that because two friends may
# have the same regex (e.g. /John/), we need to store the names in an
# array since there may be more than one. We also iterate through the
# regexes to add the most important regexes to the hash first, so
# "Jacob Evelyn" takes precedence over all instances of "Jacob" (since
# Ruby hashes are ordered).
regex_map = Hash.new { |h, k| h[k] = [] }
while !friend_regexes.empty?
friend_regexes.each do |friend_name, regex_list|
regex_map[regex_list.shift] << friend_name
friend_regexes.delete(friend_name) if regex_list.empty?
friend_regexes.each do |friend, regex_list|
regex_map[regex_list.shift] << friend
friend_regexes.delete(friend) if regex_list.empty?
end
end

# Go through the description and substitute in full, asterisk'd names for
# Go through the description and substitute full, asterisk'd names for
# anything that matches a friend's name.
new_description = description.clone
regex_map.each do |regex, names|
new_description.gsub!(regex, "**#{names.first}**") if names.size == 1
regex_map.each do |regex, friends|
if friends.size > 1 # If there are multiple matches, find best friend.
introvert.set_n_activities!
friends.sort_by! { |friend| -friend.n_activities }
end

new_description.gsub!(regex, "**#{friends.first.name}**")
end

@description = new_description
Expand Down
7 changes: 7 additions & 0 deletions lib/friends/friend.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ def serialize
"#{SERIALIZATION_PREFIX}#{name}"
end

# The number of activities this friend is in. This is for internal use only
# and is set by the Introvert as needed.
attr_writer :n_activities
def n_activities
@n_activities || 0
end

private

# Default sorting for an array of friends is alphabetical.
Expand Down
34 changes: 21 additions & 13 deletions lib/friends/introvert.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def add_activity(serialization:)
raise FriendsError, e
end

activity.highlight_friends(friends: friends)
activity.highlight_friends(introvert: self, friends: friends)
activities << activity
clean # Write a cleaned file.

Expand All @@ -102,24 +102,15 @@ def list_favorites(limit:)
raise FriendsError, "Favorites limit must be positive or unlimited"
end

# Construct a hash of friend name to frequency of appearance.
freq_table = Hash.new { |h, k| h[k] = 0 }
activities.each do |activity|
activity.friend_names.each do |friend_name|
freq_table[friend_name] += 1
end
end

# Remove names that are not in the friends list.
freq_table.select! { |name, _| friend_with_exact_name(name) }
set_n_activities! # Set n_activities for all friends.

# Sort the results, with the most favorite friend first.
results = freq_table.sort_by { |_, count| -count }
results = friends.sort_by { |friend| -friend.n_activities }

# If we need to, trim the list.
results = results.take(limit) unless limit.nil?

results.map(&:first)
results.map(&:name)
end

# List all activity details.
Expand Down Expand Up @@ -153,6 +144,23 @@ def list_activities(limit:, with:)
acts.map(&:display_text)
end

# Sets the n_activities field on each friend.
def set_n_activities!
# Construct a hash of friend name to frequency of appearance.
freq_table = Hash.new { |h, k| h[k] = 0 }
activities.each do |activity|
activity.friend_names.each do |friend_name|
freq_table[friend_name] += 1
end
end

# Remove names that are not in the friends list.
freq_table.each do |name, count|
friend = friend_with_exact_name(name)
friend.n_activities = count if friend # Do nothing if name not valid.
end
end

private

# Gets the list of friends as read from the file.
Expand Down
49 changes: 31 additions & 18 deletions test/activity_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@
let(:friend2) { Friends::Friend.new(name: "John Cage") }
let(:friends) { [friend1, friend2] }
let(:description) { "Lunch with #{friend1.name} and #{friend2.name}." }
subject { activity.highlight_friends(friends: friends) }
let(:introvert) { Minitest::Mock.new }
subject do
activity.highlight_friends(introvert: introvert, friends: friends)
end

it "finds all friends" do
subject
Expand All @@ -87,7 +90,7 @@
date_s: Date.today.to_s,
description: "Lunch with Elizabeth and John."
)
activity.highlight_friends(friends: friends)
activity.highlight_friends(introvert: introvert, friends: friends)
activity.description.
must_equal "Lunch with **#{friend1.name}** and **#{friend2.name}**."
end
Expand All @@ -97,27 +100,17 @@
date_s: Date.today.to_s,
description: "Lunch with elizabeth cady stanton."
)
activity.highlight_friends(friends: friends)
activity.highlight_friends(introvert: introvert, friends: friends)
activity.description.
must_equal "Lunch with **Elizabeth Cady Stanton**."
end

it "ignores when there are multiple matches" do
friend2.name = "Elizabeth II"
activity = Friends::Activity.new(
date_s: Date.today.to_s,
description: "Dinner with Elizabeth."
)
activity.highlight_friends(friends: friends)
activity.description.must_equal "Dinner with Elizabeth." # No match found.
end

it "ignores when at beginning of word" do
activity = Friends::Activity.new(
date_s: Date.today.to_s,
description: "Field trip to the Johnson Co."
)
activity.highlight_friends(friends: friends)
activity.highlight_friends(introvert: introvert, friends: friends)

# No match found.
activity.description.must_equal "Field trip to the Johnson Co."
Expand All @@ -128,7 +121,7 @@
date_s: Date.today.to_s,
description: "Field trip to the JimJohnJames Co."
)
activity.highlight_friends(friends: friends)
activity.highlight_friends(introvert: introvert, friends: friends)

# No match found.
activity.description.must_equal "Field trip to the JimJohnJames Co."
Expand All @@ -139,7 +132,7 @@
date_s: Date.today.to_s,
description: "Field trip to the JimJohn Co."
)
activity.highlight_friends(friends: friends)
activity.highlight_friends(introvert: introvert, friends: friends)

# No match found.
activity.description.must_equal "Field trip to the JimJohn Co."
Expand All @@ -150,7 +143,7 @@
date_s: Date.today.to_s,
description: "Dinner with **Elizabeth Cady Stanton."
)
activity.highlight_friends(friends: friends)
activity.highlight_friends(introvert: introvert, friends: friends)

# No match found.
activity.description.must_equal "Dinner with **Elizabeth Cady Stanton."
Expand All @@ -164,11 +157,31 @@
# match, because the Elizabeth isn't surrounded by asterisks.
description: "Dinner with Elizabeth**."
)
activity.highlight_friends(friends: friends)
activity.highlight_friends(introvert: introvert, friends: friends)

# No match found.
activity.description.must_equal "Dinner with Elizabeth**."
end

it "chooses the better friend when there are multiple matches" do
friend2.name = "Elizabeth II"
activity = Friends::Activity.new(
date_s: Date.today.to_s,
description: "Dinner with Elizabeth."
)

# Pretend the introvert sets the friends' n_activities values.
introvert.expect(:set_n_activities!, nil)
friend1.n_activities = 5
friend2.n_activities = 7

activity.highlight_friends(introvert: introvert, friends: friends)

# Pick the friend with more activities.
activity.description.must_equal "Dinner with **Elizabeth II**."

# introvert.verify
end
end

describe "#friend_names" do
Expand Down

0 comments on commit dd96dba

Please sign in to comment.