Page 1 of 1

table.find

Posted: Fri Sep 27, 2013 3:45 am
by lisa
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?

Re: table.find

Posted: Fri Sep 27, 2013 4:35 am
by lisa
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.

Re: table.find

Posted: Fri Sep 27, 2013 4:55 am
by lisa
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

Re: table.find

Posted: Fri Sep 27, 2013 10:37 am
by rock5
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.

Re: table.find

Posted: Fri Sep 27, 2013 1:07 pm
by Administrator
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.

Re: table.find

Posted: Fri Sep 27, 2013 6:30 pm
by lisa
I didn't know about table.contains()

If nothing else I sparked up a conversation about it =)

Re: table.find

Posted: Fri Sep 27, 2013 7:55 pm
by Administrator
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.

Re: table.find

Posted: Sat Sep 28, 2013 1:35 am
by rock5
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?

Re: table.find

Posted: Sat Sep 28, 2013 2:38 am
by lisa
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.

Re: table.find

Posted: Sun Sep 29, 2013 2:42 am
by rock5
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%.

Re: table.find

Posted: Sun Sep 29, 2013 12:58 pm
by Administrator
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.

Re: table.find

Posted: Sun Sep 29, 2013 11:04 pm
by rock5
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)