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
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)