table.find

Discuss, ask for help, share ideas, give suggestions, read tutorials, and tell us about bugs you have found with MicroMacro in here.

Do not post RoM-Bot stuff here. There is a subforum for that.
Forum rules
This is a sub-forum for things specific to MicroMacro.

This is not the place to ask questions about the RoM bot, which uses MicroMacro. There is a difference.
Post Reply
Message
Author
User avatar
lisa
Posts: 8332
Joined: Tue Nov 09, 2010 11:46 pm
Location: Australia

table.find

#1 Post by lisa » Fri Sep 27, 2013 3:45 am

I think the lack of a function for table.find is kind of annoying, what do you think about adding a function into MM for this??

Not sure how many times I think, yeah that would make life so much easier lol

It would no doubt need to be rather complex to allow for all situations in a table (lots of checks) but it should be easy enough to do.

I am thinking

return true/false, number of occurances, place/s in table it occurs

so bool, numeral, table

Any thoughts?
Remember no matter you do in life to always have a little fun while you are at it ;)

wiki here http://www.solarstrike.net/wiki/index.php?title=Manual

User avatar
lisa
Posts: 8332
Joined: Tue Nov 09, 2010 11:46 pm
Location: Australia

Re: table.find

#2 Post by lisa » Fri Sep 27, 2013 4:35 am

This might be a little messy but just to give you an idea, it does it all in strings and no number searches, so converts numbers to strings.

Code: Select all

function table.find(_table,_string)
	if type(_table) ~= "table" then return "Argument 1 must be a table" end
	if type(_string) == "number" then _string = tostring(_string) end
	if type(_string) ~= "string" then return "Incorrect usage of Argument 2" end
	local count = 0
	local found = {}
	local function _digdeep(_arg1,_arg2)
		if type(_arg1) == "table" then
			for c,d in pairs(_arg1) do
				if type(d) == "table" then
					if _digdeep(d,_arg2) then 
						return true
					end
				else
					if string.find(d,_string) then 
						return true
					end
				end
			end
		else
			_arg1 = tostring(_arg1)
			if string.find(_arg1,_string) then 
				return true
			end			
		end	
	end
	for k,v in ipairs(_table) do
		if type(v) ~= "table" then
			v = tostring(v)
			if string.find(v,_string) then 
				count = count + 1 
				table.insert(found,k)
			end
		else
			if _digdeep(v,_string) then
				count = count + 1 
				table.insert(found,k)
			end
		end
	end
	
	if count ~= 0 then
		return true, count, found
	else
		return false, count
	end
end

Code: Select all

Command> local tt = {1,{2,3},2,3,4,5} aa,bb,cc = table.find(tt,2) if aa then table.print(cc) end
table: 01A405E8
1:      2
2:      3

Command> local tt = {1,{2,3},2,3,4,5} print(table.find(tt,2))
true    2       table: 01A40390

Command> local tt = {1,{2,3},2,7,{3,2},4,5} aa,bb,cc = table.find(tt,2) if aa then table.print(cc) end
table: 01A40890
1:      2
2:      3
3:      5

Command> local tt = {1,{2,3},2,7,{3,{1,8,6,2},8,2},4,5} aa,bb,cc = table.find(tt,2) if aa then table.print(cc) end
table: 01A78F60
1:      2
2:      3
3:      5

I deliberately made it so it only counted 1 if it was a table in a table.
Example
{1,{2,3},2,7,{3,{1,8,6,2},8,2},4,5}
5th spot in the table has 2 lots of 2
{3,{1,8,6,2},8,2}
but it will only count it once, not sure if that is ideal or not.
Remember no matter you do in life to always have a little fun while you are at it ;)

wiki here http://www.solarstrike.net/wiki/index.php?title=Manual

User avatar
lisa
Posts: 8332
Joined: Tue Nov 09, 2010 11:46 pm
Location: Australia

Re: table.find

#3 Post by lisa » Fri Sep 27, 2013 4:55 am

Ok reason I posted this.

I have this

Code: Select all

	--NTYPE_WOOD = 1
	--NTYPE_ORE = 2
	--NTYPE_HERB = 3
	
	_node = 1

if database.nodes[obj.Id] and database.nodes[obj.Id].Type == _node then
which is fine if I only want to use 1 type but if I wanted to use more than 1 type then it would be more complex.

Code: Select all

	--NTYPE_WOOD = 1
	--NTYPE_ORE = 2
	--NTYPE_HERB = 3
	
	_node = {1,3}

if database.nodes[obj.Id] then
   for k,v in pairs(_node) do
     if v == database.nodes[obj.Id].Type then
But with the table.find I could do

Code: Select all

	--NTYPE_WOOD = 1
	--NTYPE_ORE = 2
	--NTYPE_HERB = 3
	
	_node = {1,3}

if database.nodes[obj.Id] and table.find(_node,database.nodes[obj.Id].Type) then
Remember no matter you do in life to always have a little fun while you are at it ;)

wiki here http://www.solarstrike.net/wiki/index.php?title=Manual

User avatar
rock5
Posts: 12173
Joined: Tue Jan 05, 2010 3:30 am
Location: Australia

Re: table.find

#4 Post by rock5 » Fri Sep 27, 2013 10:37 am

For starters, you only search indexed tables?

I'm not really sure how useful searching sub elements is going to be. I think most uses will be searching for 1 level table. Or, at most, looking for a particular sub-element of a table.

Example:
  • tt= {val1=1,val2=3,val3=7}
You might want to search for the number 7.

Or
  • tt={
    • {name="lisa",X=123,Z=234,Y=34},
      {name="rock5",X=123,Z=234,Y=34},

    }
In this case you might want to search for the records that have a name of lisa but I don't think it needs to check every value, just the name. Actually the way it should work is like table.sort that can include a function.

Eg.
  • table.find(tt,function(a) return a == 7 end)
and
  • table.find(tt,function(a) return a.name == "lisa" end)
That would make it really versatile and at the same time make it simpler. I guess you would still need to return a table of results. I would just return the results. You could use #results to see if it succeeded and how many.
  • Please consider making a small donation to me to support my continued contributions to the bot and this forum. Thank you. Donate
  • I check all posts before reading PMs. So if you want a fast reply, don't PM me but post a topic instead. PM me for private or personal topics only.
  • How to: copy and paste in micromacro
    ________________________
    Quote:
    • “They say hard work never hurt anybody, but I figure, why take the chance.”
          • Ronald Reagan

User avatar
Administrator
Site Admin
Posts: 5307
Joined: Sat Jan 05, 2008 4:21 pm

Re: table.find

#5 Post by Administrator » Fri Sep 27, 2013 1:07 pm

I'm not opposed to adding something like this. There is already a function, table.contains(), which returns true/false if a table contains a specific value. I think it should, at least, return the index of the found match, so I've added that.

Now, you also want a function that returns the number of occurrences. Could also be useful, I suppose. I think it would be important to separate these two (if not different functions then a switch to enable it, default to disabled) due to 99% of the time it being just a waste since you'll normally only want to know if the table contains a value.

User avatar
lisa
Posts: 8332
Joined: Tue Nov 09, 2010 11:46 pm
Location: Australia

Re: table.find

#6 Post by lisa » Fri Sep 27, 2013 6:30 pm

I didn't know about table.contains()

If nothing else I sparked up a conversation about it =)
Remember no matter you do in life to always have a little fun while you are at it ;)

wiki here http://www.solarstrike.net/wiki/index.php?title=Manual

User avatar
Administrator
Site Admin
Posts: 5307
Joined: Sat Jan 05, 2008 4:21 pm

Re: table.find

#7 Post by Administrator » Fri Sep 27, 2013 7:55 pm

Indeed. I think I'm going to have to side against nested searching. If it were needed, a recursion function could be written up quickly to take care of it. The problem here is that it is slow and complex (relatively) by nature and would be awkward to make flexible enough to fit all cases in one function.

User avatar
rock5
Posts: 12173
Joined: Tue Jan 05, 2010 3:30 am
Location: Australia

Re: table.find

#8 Post by rock5 » Sat Sep 28, 2013 1:35 am

Here's my take on it.

Code: Select all

function table.find (_table, _strOrFunc)
	local str,func
	if type(_table) ~= "table" then print("Argument 1 must be a table") end
	if type(_strOrFunc) == "number" or type(_strOrFunc) == "string" then
		str = _strOrFunc
	elseif type(_strOrFunc) == "function" then
		func = _strOrFunc
	else
		print("Incorrect usage of Argument 2", type(_strOrFunc))
	end

	found = {}
	for k,v in pairs(_table) do
		if str then
			if string.find(v,str) then
				table.insert(found,k)
			end
		else -- must be func
			if func(k,v) then
				table.insert(found,k)
			end
		end
	end
	
	return found
end
For the second argument it accepts strings or numbers to do a regular 1 level search, or an eval function. The function should accept key and value and return true or false. The function returns a table of indexes.

Example of a regular search.

Code: Select all

Command> names={"lisa","roc","rock5","bill d cat"} f=table.find(names,"roc")
Command> print("Count",#f) table.print(f)
Count   2
table: 05754BE8
1:      2
2:      3
Example of an evalfunc search for NPC locations.

Code: Select all

Command> ol=CObjectList() ol:update() f=table.find(ol.Objects,function(k,v) retu
rn v.Name=="Cadoon Guard" end)
Command> for k,v in pairs(f) do p=ol.Objects[v] printf("Name: %s, XZY: %d, %d, %
d\n",p.Name,p.X,p.Z,p.Y) end
Name: Cadoon Guard, XZY: 3914, -7931, 447
Name: Cadoon Guard, XZY: 3816, -7916, 448
Name: Cadoon Guard, XZY: 3850, -7908, 444
Name: Cadoon Guard, XZY: 3852, -7913, 444
Now that I included the key value in the evalfunc you can also search for keys. This example lists all the player values that include 'hp' in it.

Code: Select all

Command> hp=table.find(player,function(k,v) return string.find(string.lower(k),"
hp") end) table.print(hp)
table: 05755EF8
1:      PotionLastHpEmptyTime
2:      HP
3:      PhiriusHpUsed
4:      PotionHpUsed
5:      PhiriusLastHpEmptyTime
6:      PotionLastHpOnceEmptyTime
7:      PotionHpOnceUsed
8:      MaxHP
So what do you think of my take?
  • Please consider making a small donation to me to support my continued contributions to the bot and this forum. Thank you. Donate
  • I check all posts before reading PMs. So if you want a fast reply, don't PM me but post a topic instead. PM me for private or personal topics only.
  • How to: copy and paste in micromacro
    ________________________
    Quote:
    • “They say hard work never hurt anybody, but I figure, why take the chance.”
          • Ronald Reagan

User avatar
lisa
Posts: 8332
Joined: Tue Nov 09, 2010 11:46 pm
Location: Australia

Re: table.find

#9 Post by lisa » Sat Sep 28, 2013 2:38 am

looks better than my version and I can also see that it has more uses aswell than my version.

I wonder how the speed would compare to previous ways of doing things.
Remember no matter you do in life to always have a little fun while you are at it ;)

wiki here http://www.solarstrike.net/wiki/index.php?title=Manual

User avatar
rock5
Posts: 12173
Joined: Tue Jan 05, 2010 3:30 am
Location: Australia

Re: table.find

#10 Post by rock5 » Sun Sep 29, 2013 2:42 am

I did a bit of testing.

My find

Code: Select all

Command> ol=CObjectList() ol:update() local st=getTime() f=table.find(ol.Objects
,function(k,v) return string.match(v.Name,"Foundation") end) st=deltaTime(getTim
e(),st) print("time took",st)
time took       0.2405154949447
Custom function

Code: Select all

Command> ol=CObjectList() ol:update() local st=getTime() f={} for k,v in pairs(o
l.Objects) do if string.match(v.Name,"Foundation") then table.insert(f,k) end en
d st=deltaTime(getTime(),st) print("time took",st)
time took       0.10131449167582
Hm.. 2.5 times longer. It's because of the 'if's in the loop.

This should be better.

Code: Select all

function table.find (_table, _strOrFunc)
	local str,func
	if type(_table) ~= "table" then print("Argument 1 must be a table") end
	if type(_strOrFunc) == "number" or type(_strOrFunc) == "string" then
		str = _strOrFunc
	elseif type(_strOrFunc) == "function" then
		func = _strOrFunc
	else
		print("Incorrect usage of Argument 2", type(_strOrFunc))
	end

	found = {}

	if str then
		for k,v in pairs(_table) do
			if string.find(v,str) then
				table.insert(found,k)
			end
		end
	else -- must be func
		for k,v in pairs(_table) do
			if func(k,v) then
				table.insert(found,k)
			end
		end
	end

	return found
end

Code: Select all

Command> ol=CObjectList() ol:update() local st=getTime() f=table.find(ol.Objects
,function(k,v) return string.match(v.Name,"Foundation") end) st=deltaTime(getTim
e(),st) print("time took",st)
time took       0.16176263376812
That's better and I think quite acceptable considering they are both under a ms.

And I just tested the overhead of the function, that's everything except the actual search, took 0.02ms which means the actual search difference is more like 0.1ms to 0.14ms. So 40%.
  • Please consider making a small donation to me to support my continued contributions to the bot and this forum. Thank you. Donate
  • I check all posts before reading PMs. So if you want a fast reply, don't PM me but post a topic instead. PM me for private or personal topics only.
  • How to: copy and paste in micromacro
    ________________________
    Quote:
    • “They say hard work never hurt anybody, but I figure, why take the chance.”
          • Ronald Reagan

User avatar
Administrator
Site Admin
Posts: 5307
Joined: Sat Jan 05, 2008 4:21 pm

Re: table.find

#11 Post by Administrator » Sun Sep 29, 2013 12:58 pm

Looks good to me. Now, the only thing is with the string search, do you want to default to allowing substrings (ie. "roc" matching in "rock5" per your example), or should it only allow full matches? What about regular expressions?

Allowing the regular expressions makes it more powerful, but then opens up the possibility of people overlooking that and using pattern characters which could give them some strange results. I'm not against it, just need to consider it.

User avatar
rock5
Posts: 12173
Joined: Tue Jan 05, 2010 3:30 am
Location: Australia

Re: table.find

#12 Post by rock5 » Sun Sep 29, 2013 11:04 pm

Initially I would have just done exact matches but I used the substring search because Lisa did. If we include substring searches I think it would be better without regex to make it more reliable to regular users who don't know about pattern characters. If we need to use regex we could always use the eval function.

I used this function recently and realized that it might not be as useful as it first seems. The idea of this function is to replace the for loop, looping through a table, with a function call. But after you get your results you have to create a for loop anyway to iterate through the results. If all you did with a for loop was search for something then the function will save time but usually, in many cases, a needed action is performed on the found item inside the for loop. In that case the function could actually make the code longer :D Lets see if I can make up an example.

Old way

Code: Select all

for _, item in pairs(inventory.BagSlot) do
    for _, toss in pairs(DiscardList) do
        if string.find(item,toss) then
            item:delete()
            break
        end
    end
end
New way

Code: Select all

function eval(k,v)
    if table.find(DiscardList,v.Name) then
        v:delete
    end
end
items = table.find(inventory.BagSlot,eval)
My example ended up having 2 for loops which made it interesting. And I came up with a solution that didn't need to iterate through the results because I deleted the items in the eval function. That ended up taking fewer lines. Technically it would take longer because it doesn't break when it finds a result in the DiscardList.

Maybe we should include an option to return the first match if the user expects only one match or only needs the first match. Maybe
  • table.find(_table,_val,_firstmatchonly)
  • Please consider making a small donation to me to support my continued contributions to the bot and this forum. Thank you. Donate
  • I check all posts before reading PMs. So if you want a fast reply, don't PM me but post a topic instead. PM me for private or personal topics only.
  • How to: copy and paste in micromacro
    ________________________
    Quote:
    • “They say hard work never hurt anybody, but I figure, why take the chance.”
          • Ronald Reagan

Post Reply

Who is online

Users browsing this forum: No registered users and 12 guests