Created
July 30, 2018 10:34
-
-
Save MatMoore/9b590c962df233aa998f4608e3c7288d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Full code here: | |
# https://github.com/googleads/google-api-ads-ruby/blob/master/adwords_api/examples/solutions/conflicting_keywords/conflicting_keywords.rb | |
def compare_keywords(negatives, positive) | |
negatives.each do |negative| | |
match_type = negative.match_type.downcase | |
negative_text = negative.text.downcase | |
positive_text = positive.text.downcase | |
match = false | |
# If the negative keyword is more strict than the positive one, it cannot | |
# match. | |
# E.g. a negative exact "cool shoe" will not prevent positive phrase | |
# "cool shoe shine". | |
positive_match_type = positive.match_type.downcase | |
next if positive_match_type == 'broad' && match_type != 'broad' | |
next if positive_match_type == 'phrase' && match_type == 'exact' | |
# Exact matching with negative keywords triggers only when the full text of | |
# the keywords is exactly the same. | |
# E.g. a negative "silk scarves" will only match "silk scarves", not | |
# "red silk scarves". | |
if match_type == 'exact' | |
match = (negative_text == positive_text) | |
end | |
# Phrase matching with negative keywords triggers when the negative phrase | |
# is present in the target, completely unmodified. | |
# E.g. a negative "silk scarves" will match "gift silk scarves", but not | |
# "silk gift scarves". | |
if match_type == 'phrase' | |
negative_tokens = negative_text.split(' ') | |
positive_tokens = positive_text.split(' ') | |
positive_tokens.each_with_index do |positive_token, positive_index| | |
# Iterate until the current token matches the first token in the | |
# negative keyword. | |
if positive_token == negative_tokens.first | |
candidate_match = true | |
# Do all of the subsequent tokens also match? | |
negative_tokens[1..-1].each_with_index do |token, index| | |
if token != positive_tokens[positive_index + index + 1] | |
candidate_match = false | |
break | |
end | |
end | |
match = candidate_match | |
end | |
end | |
end | |
# Broad matching with negative keywords triggers when all of the words are | |
# present and exactly the same. | |
# E.g. a negative "silk scarves" will match "silk gift scarves", but not | |
# "wool scarves". | |
if match_type == 'broad' | |
negative_tokens = negative_text.split(' ') | |
positive_tokens = positive_text.split(' ') | |
candidate_match = true | |
negative_tokens.each do |token| | |
if !positive_tokens.include?(token) | |
candidate_match = false | |
break | |
end | |
end | |
match = candidate_match | |
end | |
negative.add_blocked(positive) if match | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def compare_keywords(negatives, positive): | |
for negative in negatives: | |
match_type = negative.match_type.downcase | |
negative_text = negative.text.downcase | |
positive_text = positive.text.downcase | |
positive_match_type = positive.match_type.downcase | |
match = false | |
if positive_match_type == 'broad' && match_type != 'broad': | |
continue | |
if positive_match_type == 'phrase' && match_type == 'exact': | |
continue | |
if match_type == 'exact': | |
match = (negative_text == positive_text) | |
if match_type == 'phrase': | |
negative_tokens = negative_text.split(' ') | |
positive_tokens = positive_text.split(' ') | |
for positive_token, positive_index in positive_tokens: | |
# Iterate until the current token matches the first token in the | |
# negative keyword. | |
if positive_token == negative_tokens.first: | |
candidate_match = true | |
# Do all of the subsequent tokens also match? | |
for token, index in negative_tokens[‘1..-1’]: | |
if token != positive_tokens[positive_index + index + 1]: | |
candidate_match = false | |
match = candidate_match | |
if match_type == 'broad': | |
negative_tokens = negative_text.split(' ') | |
positive_tokens = positive_text.split(' ') | |
candidate_match = true | |
for token in negative_tokens: | |
if positive_tokens.include(token): | |
candidate_match = false | |
match = candidate_match | |
negative.add_blocked(positive) if match |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Things that jump out at me:
Syntax
&&
in ruby isand
in python.downcase
is.lower()
for positive_token, positive_index in positive_tokens:
should befor positive_index, positive_token in enumerate(positive_tokens):
. The enumerate function takes a sequence and transforms it to a sequence of pairs, where the first item is the index and the second is the value.negative_tokens[‘1..-1’]
should benegative_tokens[1:]
I think - read up on slices if it turns out to be off by onepositive_tokens.include(token)
should betoken in positive_tokens
negative.add_blocked(positive) if match
isn't valid python - you need to do a full if statement like aboveAlso - if you install a command line tool like flake8 it can report small errors like this without having to run the code 👍
Inputs and outputs
The function assumes you're passing it a list of objects with the following methods:
In the ruby version
add_blocked
is implemented like this:which basically says "add the blocked keywords to a list associated with the negative keyword, but only keep the first 5 if this is called multiple times"
So to use the function you'll need something equivalent to this to be defined in your python code somewhere:
if it's already part of a library you're using then you'll just need to import it wherever you call the function, but you might need to change how it's used in the function if it doesn't match up with the ruby version (eg if the properties have different names).
Testing
As long as you have those classes defined, you should be able to test it with something like this:
I recommend using something like pytest for writing tests as it will let you define multiple tests and show you which ones pass or fail: https://docs.pytest.org/en/latest/getting-started.html
You can write tests for each match_type to make sure the logic works as you expect (or if you aren't using all the match types then you could just delete that part of that code)