Thread Tools Display Modes
05-30-09, 06:29 PM   #1
Nirrudn
A Deviate Faerie Dragon
AddOn Author - Click to view addons
Join Date: Nov 2005
Posts: 17
Large Scale Pattern Matching

I have a chat filter addon, and I'm looking to block out about 30 specific, yet slightly variable, messages. The variable parts of them are most likely to be player or pet names.

I'm a relative programming noob, so my initial instinct is just a giant block of "if strfind(text, pattern1) return true" type stuff. But that strikes me as both inefficient and messy to maintain/add to. So I come to you good people seeking a better way to accomplish this task.

I had this initial thought to make things at least easier to maintain, if not more efficient:

Code:
local filterPatterns = {
"%a+ throws the ball to.*", "%a+ catches the ball!", etc. etc. };
for i=1, #filterPatterns do
if strfind(text, filterPatterns[i]) then return true; end
end
I guess the real question is will this show a noticeable performance decrease at 30 filters? 50? 100? Since it's a chat addon it won't be running constantly via OnUpdate or anything, but it may run quite a bit in a talkative situation.

The other option I thought of was, since my variable factors are most likely to be units, to check each word using string.gmatch against UnitExists, then substitute that word with a pattern, which would allow me to use a table with my patterns as keys, I think.

Code:
local filterPatterns = {
["%a+ throws the ball to.*"] = true, ["%a+ catches the ball!"] = true, etc. etc. };
local tmpText = text; --Unsure if it's okay to change text while iterating over it or not for word in string.gmatch(text, "%a+") do
if UnitExists(word) then
tmpText = string.gsub(tmpText, word, "%a+");
end
end text = tmpText; return filterPatterns[text];
The issues I see with this though are times where UnitExists might return nil, such as /yells.

Or am I just overcomplicating the whole matter and there won't be any problems with doing dozens of strfinds on each chat message that comes in? Thanks for any and all advice/comments.
  Reply With Quote
05-30-09, 07:49 PM   #2
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
I'm not sure why you need to compare anything against UnitExists. What is your overall goal with the pattern matching?

Otherwise, the most efficient way to do what you posted is to use an ipairs() loop:

Code:
local filterPatterns = {
     "^%a+ throws the ball to.*",
     "^%a+ catches the ball!",
}

for i, pattern in ipairs(filterPatterns) do
     if text:match(pattern) then
          -- do stuff
     end
end
  Reply With Quote
05-30-09, 10:58 PM   #3
Nirrudn
A Deviate Faerie Dragon
AddOn Author - Click to view addons
Join Date: Nov 2005
Posts: 17
Originally Posted by Phanx View Post
I'm not sure why you need to compare anything against UnitExists. What is your overall goal with the pattern matching?

Otherwise, the most efficient way to do what you posted is to use an ipairs() loop:

Code:
local filterPatterns = {
     "^%a+ throws the ball to.*",
     "^%a+ catches the ball!",
}

for i, pattern in ipairs(filterPatterns) do
     if text:match(pattern) then
          -- do stuff
     end
end
I was kicking around the concept of playing with UnitExists to basically convert any unit names in the text into a pattern expression to create a more "universal" way of filtering things without a giant "if...then" block/loop, as I'd read referencing a table entry was more efficient than a bunch of sequential checks. So my whole goal was to reduce any text down into a pattern and avoid/minimize if...then blocks. I can't think of a good way to do that though, but the method you suggested is better than what I was thinking!

Edit - More clarification since I think I did a poor job at explaining what I wanted to do:

"Joebob throws the ball to Mary" would get converted into "%a+ throws the ball to %a+", and instead of looping through my table comparing each table value to my text via text:match, then if it matches returning true, I could just use the raw pattern I had converted it to as a key, and that keyed tabled entry would be my true value, letting me just do a return filterPatterns[text];. (And it occurs to me now my original example filters would have to be changed for this to work)

Thanks for the help!

Last edited by Nirrudn : 05-30-09 at 11:10 PM.
  Reply With Quote
05-31-09, 12:29 PM   #4
Phanx
Cat.
 
Phanx's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2006
Posts: 5,617
Originally Posted by Nirrudn View Post
"Joebob throws the ball to Mary" would get converted into "%a+ throws the ball to %a+", and instead of looping through my table comparing each table value to my text via text:match, then if it matches returning true, I could just use the raw pattern I had converted it to as a key, and that keyed tabled entry would be my true value, letting me just do a return filterPatterns[text];. (And it occurs to me now my original example filters would have to be changed for this to work)
Just do this:

Code:
local filterPatterns = {
     "^%a+ throws the ball to ",
     "^%a+ catches the ball!",
}

local function filter(frame, event, message, sender, ...)
     for i, pattern in ipairs(filterPatterns) do
          if text:match(pattern) then
               -- do stuff
          end
     end
end

ChatFrame_AddMessageFilter("CHAT_MSG_TEXT_EMOTE", filter)
ChatFrame_AddMessageFilter("CHAT_MSG_YELL", filter)
-- repeat with all the chat message types you want to watch
I wouldn't worry about trying to detect unit names or anything else like that, unless you're wanting to keep track of how many times Joebob throws balls at Mary. While doing a single table lookup is indeed faster than multiple string match operations, unless you're expecting to see the same message many times (i.e. you'll see a lot of "Joebob throws the ball to Mary." and don't expect to see much of "Tom throws the ball to Jeff."), you still have to do the string matches to populate the table. I guess you could use a metatable:

Code:
local filterPatterns = {
     "^%a+ throws the ball to ",
     "^%a+ catches the ball!",
}

local filterMessages = setmetatable({}, { __index = function(t, message)
     for i, pattern in ipairs(filterPatterns) do
          if message:match(pattern) then
               t[message] = true
               return true
          end
     end
     t[message] = false
     return false
end })

local function filter(frame, event, message, sender, ...)
     if filterMessages[message] then
          -- do stuff
     end
end

ChatFrame_AddMessageFilter("CHAT_MSG_TEXT_EMOTE", filter)
ChatFrame_AddMessageFilter("CHAT_MSG_YELL", filter)
-- repeat with all the chat message types you want to watch
In general, I'm still not sure what you actually want to do. Do you want to hide messages matching one of the patterns completely? Do you want to modify the messages in some way? Do you want to just note that you've seen then and then display them unchanged?
  Reply With Quote
05-31-09, 06:26 PM   #5
Nirrudn
A Deviate Faerie Dragon
AddOn Author - Click to view addons
Join Date: Nov 2005
Posts: 17
Originally Posted by Phanx View Post
In general, I'm still not sure what you actually want to do. Do you want to hide messages matching one of the patterns completely?
Yup, that's all I want to do is remove whatever matches my patterns. I have a tendency to overcomplicate things because I always like trying out new techniques or stuff I just learned.

Thanks for taking all the time to help me, it's been much appreciated and very informative for me.
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » Large Scale Pattern Matching


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off