|
Joined: May 2008
Posts: 127
Vogon poet
|
OP
Vogon poet
Joined: May 2008
Posts: 127 |
I just started learning hash tables and it looks easy, I just can't figure out exactly how to use them and add data to tables.
I currently have an IP script which stores all user IP's in an INI file. Now that there are more users stored in the INI, it takes longer to retrieve information from the INI as its size increases. I've made a script that literally completely converts my method of IP storage from INI's to hash tables, but I can't see how adding data to tables work.
Some users have dynamic IP (hence multiple IP's must be stored under their name), and others have static. I'm trying to add the information to the tables like this: /hadd userips <nick> <ip>, which adds the first IP, but then when I go to add another, it overwrites the current IP with the new one even when the IP is different. So I guess my question is why can't I add multiple values? How can I store IP's using hash tables since my method doesn't work?
Any help is always appreciated. Thanks =)
|
|
|
|
Joined: Aug 2004
Posts: 7,252
Hoopy frood
|
Hoopy frood
Joined: Aug 2004
Posts: 7,252 |
You can add multiple values for the situation that you're describing by using a combination of /hadd, $hget, and $addtok The format you're looking for would look like: /hadd -m Table $nick $addtok($hget(Table,$nick),$address,32) If you don't understand that, here's a simplified, but multiple line version of the same thing. var %addresses = $hget(Table,$nick)
var %addresses = $addtok(%addresses,$address,32)
/hadd -m Table $nick %addresses See /help /hadd /help $hget and /help $addtok This is the easiest way of doing what you're asking, but there are other options. As to why the IP address was over-written, since you're familiar with INI files I'll use an INI file comparison. You use /writeini -n file.ini section item data to write information to the ini file. Now, if you specify the same section and item, but different data, the data that was in the ini file gets over-written Hash tables can directly load sections from an ini file using the -i switch. This makes each item from the ini file the item in the hash table, and the same with the data, but the information regarding what section is not retained in the hash table.
|
|
|
|
Joined: May 2008
Posts: 127
Vogon poet
|
OP
Vogon poet
Joined: May 2008
Posts: 127 |
Ok, I understand how to use the tables now. In my current script, there is a feature that loops through the entire INI and returns the name of the person who used the specified IP.
Is it possible to loop through the table too? I know there's $hfind, but I imagine that would only return the first result it finds, correct?
|
|
|
|
Joined: Oct 2005
Posts: 1,741
Hoopy frood
|
Hoopy frood
Joined: Oct 2005
Posts: 1,741 |
You can loop through hash tables as well by using $hfind(table,*,0,w) to get the number of entries in the hash. However, if you choose to store your data differently in the hash table, you can eliminate constantly looping through each entry. For example, if you wanted to know who has used a certain IP before, instead of using $nick as the ITEM and a list of addresses as the DATA, you could use a single address as the ITEM and a list of $nick's as the DATA.
12.34.56.78 = Bob,Sam,Mike,Betty 23.45.67.89 = Alan,Steve,Sally,Betty
Using that format if the address you were looking for was stored in %addr, you could find the list of names by using $hget(table,%addr).
If you are looking to be able to search for who has used a certain IP before, AND what IPs a certain person has used, I would recommend storing the info in the hash table BOTH ways. By using some kind of distinguishing prefix in the ITEM, you can store multiple sets of data in a single table. Example:
i-12.34.56.78 = Bob,Sam,Mike,Betty i-23.45.67.89 = Sam,Steve,Sally,Betty n-Bob = 12.34.56.78 n-Sam = 12.34.56.78,23.45.67.89 n-Mike = 12.34.56.78 n-Betty = 12.34.56.78 n-Steve = 23.45.67.89 n-Sally = 23.45.67.89
To find out who has used an IP, as above, you would now use $hget(table,$+(i-,%addr)). And to find out what IPs a certain nick has used, you would type $hget(table,$+(n-,%nick)).
It is much more efficient (in terms of speed) to do all the work twice when you are adding the data, rather than 10000 times each time you need to loop through every entry to find some data.
-genius_at_work
|
|
|
|
Joined: Jan 2007
Posts: 1,156
Hoopy frood
|
Hoopy frood
Joined: Jan 2007
Posts: 1,156 |
I agree with Genius. Regardless, $hget(table,0).item will return total items. var %r = $hget(table,0).item | while (%r) {
dec %r
}
|
|
|
|
Joined: May 2008
Posts: 127
Vogon poet
|
OP
Vogon poet
Joined: May 2008
Posts: 127 |
genius_at_work, it's a good idea but it'd be too much work. The INI has almost 2,000 lines and adding that much information to a hash table the way you're describing would take a long time.
This is what my hash table looks like: Table: userips Items: <nicks> (almost 2,000 nicks to be exact) Values: IP(s) (which are separated by commas if there are multiple IP's)
So say if I did !searchip <nick>, I would use $hget(userips,$2) which would return all the IP's that user has used.
But the part I can't get working is if you search for an IP, it should return the nick (or the item name in the table). For example: !searchip 1.2.3.4
...would return all item names that have 1.2.3.4 anywhere in the value. I don't want to return the number of items (which btw still didn't work, I tried using what you said and it returned 0 when I searched for my IP and my IP exists in many items), but I want to return the actual name of the item.
|
|
|
|
Joined: Aug 2004
Posts: 7,252
Hoopy frood
|
Hoopy frood
Joined: Aug 2004
Posts: 7,252 |
on *:text:!searchip <nick>:*:{
if $hget(userips,$1) { .msg $nick $1 $+ 's IPs are $v1 }
elseif !$hfind(userips,$+(*,$1,*),0,w) { .msg $nick No match found for $1 }
else {
var %a = 1, %b = $hfind(userips,$+(*,$1,*),0,w)
.msg $nick Matches for $1 are:
while %a <= %b {
.timer 1 %a msg $nick $hfind(userips,$+(*,$1,*),%a,w)
inc %a
}
}
} Dejavu is kicking in with this topic, yet I'm unable to find the topic that I'm (almost) certain I gave almost identical replies to.
|
|
|
|
Joined: Oct 2005
Posts: 1,741
Hoopy frood
|
Hoopy frood
Joined: Oct 2005
Posts: 1,741 |
It would be very little work to transfer from an INI file to the dual-format HASH table that I described above.
alias initohash {
var %if = myfile.ini, %is = mysection
var %hn = myhash, %hs = 100
;;;;
if ($hget(%hn)) {
if ($?!="Hash Table ' %hn ' already exists. $crlf $+ OVERWRITE the existing data?") hfree %hn
else return
}
var %ts = $ticks
hmake %hn %hs
var %ii, %id, %di, %dk, %hi, %hk
var %a = 0, %aa = $ini(%if,%is,0)
while (%a < %aa) {
inc %a
%ii = $ini(%if,%is,%a)
%id = $readini(%if,%is,%ii)
var %b = 0, %bb = $numtok(%id,44)
while (%b < %bb) {
inc %b
%di = $gettok(%id,%b,44)
%dk = %ii
%hi = $hget(%hn,%dk)
hadd %hn %dk $addtok(%hi,%di,44)
%hk = $hget(%hn,%di)
hadd %hn %di $addtok(%hk,%dk,44)
}
}
echo -at Transferred %if to %hn in $calc(($ticks - %ts) / 1000) $+ s
}
This code assumes that your ini file is structured like this:
[OnlyOneSection] nick1=1.2.3.4 nick2=2.3.4.5 nick3=3.4.5.6,4.5.6.7,5.6.7.8 nick4=1.2.3.4,4.5.6.7 nick5=4.5.6.7
Set the variables at the top of the alias, then call /initohash. It will transfer into the hash table like this:
1.2.3.4 nick1,nick4 3.4.5.6 nick3 5.6.7.8 nick3 2.3.4.5 nick2 4.5.6.7 nick3,nick4,nick5
nick5 4.5.6.7 nick4 1.2.3.4,4.5.6.7 nick1 1.2.3.4 nick3 3.4.5.6,4.5.6.7,5.6.7.8 nick2 2.3.4.5
To get a list of IPs used by %nick: echo -a $hget(myhash,%nick) To get a list of nicks who used %addr: echo -a $hget(myhash,%addr) -genius_at_work
|
|
|
|
Joined: May 2008
Posts: 127
Vogon poet
|
OP
Vogon poet
Joined: May 2008
Posts: 127 |
@RusselB: Doesn't work as expected. Joe_Dean · !hget Joe_Dean Naed · Joe_Dean's IPs are 76.106.215.49 Joe_Dean · !hget 76.106.215.49 Naed · No match found for 76.106.215.49 Joe_Dean · !hget 76.106.215 Naed · No match found for 76.106.215 Joe_Dean · !hget 76.106 Naed · No match found for 76.106 Joe_Dean · !hget 76 Naed · Matches for 76 are: Naed · Guest769 Naed · t12P76G Naed · kwiatu1767 Naed · t12P76Gnew - Doesn't show names using my IP when I reference my IP. - Doesn't support wildcard searches (also it kinda does when you only specify the first octet, but still doesn't show all names using that range). @genius: I really appreciate the help and it sounded like a great idea, but I don't think I like it so much. I also believe there's something wrong in your code. It returns the same name twice sometimes and also returns IP addresses when I reference a range. Plus, it returns every other name using the IP except mine. Bah, this shit is confusing the heck out of me. There isn't a simpler way? My original idea was to store the nicks as the table items and the IP's as the values separated by commas (if there are multiple IP's). How do I do a wildcard search in the value and return the item name? Would make things much much easier.
|
|
|
|
Joined: Aug 2004
Posts: 7,252
Hoopy frood
|
Hoopy frood
Joined: Aug 2004
Posts: 7,252 |
oops.. thought there was something missing. Wildcard searches are not included in my code, as you didn't specify that you wanted/needed that option, and it will make the code more difficult. I'll think about the alterations required to allow for wildcard searches, but in the mean time, try this version on *:text:!searchip <nick>:*:{
if $hget(userips,$1) { .msg $nick $1 $+ 's IPs are $v1 }
elseif !$hfind(userips,$+(*,$1,*),0,w).data { .msg $nick No match found for $1 }
else {
var %a = 1, %b = $hfind(userips,$+(*,$1,*),0,w).data
.msg $nick Matches for $1 are:
while %a <= %b {
.timer 1 %a msg $nick $hfind(userips,$+(*,$1,*),%a,w).data
inc %a
}
}
}
|
|
|
|
Joined: Nov 2006
Posts: 1,559
Hoopy frood
|
Hoopy frood
Joined: Nov 2006
Posts: 1,559 |
The problem with a wildcard scan is that it will ignore the comma delimiters, and match on the whole data part. Here's a "rough" hfind(r) [regex search] approach; I adapted these regexes from this script. It may not be optimized for performance (one would have to follow Genius' suggestions), and the output may be improved with some $addtok-routine instead of a "one nick - one line" play. Another idea would be to limit a max. number of matches (prevent playback of the whole dataset if someone does a !serchip *.*.*.* or the like)... on *:text:!searchip &:*: {
if (. !isin $2) {
if (($2 !isnum) && ($hget(userips,$2))) { .msg $nick $2 $+ 's IPs are: $v1 }
else { .msg $nick No matches for nick $qt($2) }
}
else {
; $1 is a valid (wildcard-)IP "A.A.A.A" (where each A is 1-3 digits/wildcards)
var %isipreg = /^(?:(?:\d|\*|\?){1,3}\.){3}(\d|\*|\?){1,3}$/
if ($regex($2,%isipreg)) {
; replaces to build a regex that will match wild-IP $2 in a comma-delimited string of IPs
var %findreg = $replace($regsubex($2,/\*+/g,*),.,\.,*,\d*,?,\d)
var %findreg = $+(/(?<=^|\54),%findreg,(?=$|\54)/)
write -c searchip.txt
; perform hfind loop, write results to a file
var %n = 1
while ($hfind(userips,%findreg,%n,r).data) {
write searchip.txt $v1
inc %n
}
var %l = $lines(searchip.txt)
if (%l = 0) { .msg $nick There are no matches for IP $qt($2) }
else {
; sort file, play results to nick
filter -ffct 1 32 searchip.txt searchip.txt
.msg $nick There $iif((%l == 1),is %l match,are %l matches) for IP $qt($2) :
play -m2 $nick searchip.txt 300
}
}
else { .msg $nick Wrong Syntax. use !searchip <nick> or !searchip <IP>. <IP> has to be N.N.N.N and may contain wildcards. }
}
}
...hope it's of use
Last edited by Horstl; 29/11/08 11:14 PM.
|
|
|
|
Joined: May 2008
Posts: 127
Vogon poet
|
OP
Vogon poet
Joined: May 2008
Posts: 127 |
Ehh, I have a problem. What would cause all my tables to be automatically deleted when the client is opened/closed? I had a table, then restarted my computer and opened mIRC to find the table was gone.
Any ideas?
|
|
|
|
Joined: Aug 2004
Posts: 7,252
Hoopy frood
|
Hoopy frood
Joined: Aug 2004
Posts: 7,252 |
mIRC does not retain hash table information when the client is closed. I use the ON EXIT event to save hash tables to the hard drive so that the data is not lost. Additionally, hash tables are stored in RAM, so when you restarted your computer, the ram was cleared. This is par of the reason why I (usually) have the following in any script that uses a hash table on *:start:{
if !$hget(TABLE) { .hmake TABLE 100 }
if $exists($scriptdirTABLE.hsh)) { .hload TABLE $scriptdirTABLE.hsh }
}
on *:exit:{
.hsave TABLE $scriptdirTABLE.hsh
}
on *:disconnect:{
.hsave TABLE $scriptdirTABLE.hsh
} Replace TABLE with the actual name of the hash table you want referenced.
|
|
|
|
Joined: May 2008
Posts: 127
Vogon poet
|
OP
Vogon poet
Joined: May 2008
Posts: 127 |
And what happens when the client stops responding and you have to end the task, or if your power goes out, etc?
|
|
|
|
Joined: Oct 2005
Posts: 1,741
Hoopy frood
|
Hoopy frood
Joined: Oct 2005
Posts: 1,741 |
You can run a repeating timer that periodically saves the hash table to file. The frequency of the timer will depend on how often you change the contents of the file, and how worried you are about potentially losing a few entries in the table.
-genius_at_work
|
|
|
|
Joined: Jan 2007
Posts: 1,156
Hoopy frood
|
Hoopy frood
Joined: Jan 2007
Posts: 1,156 |
Just as an aside and an opinion. I use hash tables a lot and I never use hfind. I create my hash tables to return information directly.
For instance, I choose the most common denominator for the item. Take user managment. On some servers the nick will never change, on others you can grab a persons IP. On my server we use a gatekeeper address assigned by the server.
I know they can change their nick and everything, but that account will always have the same gatekeeper, so I set the item as the gatekeeper.
hadd table gatekeeper data
So now, $hget(table,$address) returns all data I collected on the user. I save all the data in the same order so I can use $gettok or something to choose what I want from the data.
The point I am trying to make is that I design my tables to be accessed quickly and easily. I think there is a value in considering this tactic.
I'm not saying there is anything wrong with $hfind, I just consider directly calling the item in the hash table a bit faster and less work for mIRC.
Edit: Sorry this wasn't a reply to Russelb in particular, it was in response for the request to search for wildcards in a hash table.
Last edited by DJ_Sol; 03/12/08 04:26 AM.
|
|
|
|
Joined: May 2008
Posts: 127
Vogon poet
|
OP
Vogon poet
Joined: May 2008
Posts: 127 |
Ok genius, I did what you said and it's all working fine. There's a small thing that may become an issue sooner or later though. When the bot closes or disconnects, it saves the table to the INI file. On load it loads the INI, etc (as you already know).
But the thing is, it's loading the INI to the hash and the INI has over 10k lines which will probably soon be 20k, etc. The number of lines will just keep increasing and the time it takes to load the INI into the hash will take even longer.
My idea was on disconnect or exit, look at the current INI and use $lines to see how many lines it has. If it exceeds 10k lines, create a second INI and on load, it could just load all INI's. But I would need the script to know which INI was the last INI to be created so it could check that file to see if it exceeds the line limit which will determine whether or not it needs to create a new INI.
So if I named the INI's: userips1.ini, userips2.ini, userips3.ini, etc, how could I make the script know that 'userips3.ini' was the last file to be created?
|
|
|
|
Joined: Aug 2004
Posts: 7,252
Hoopy frood
|
Hoopy frood
Joined: Aug 2004
Posts: 7,252 |
I may have missed seeing this, but are you specifying the section when loading/saving the hash table from/to the ini file?
If you aren't, then why not just use the /hsave command, and not worry about the fact that you're currently using an ini file.
This, imo, has the following advantages: 1) There's no need to worry about the number of lines in the file 2) You don't have to worry about the 64k "limit" on ini files.
Regarding speed, I have a bot which currently has 8 hash tables, of which 3 of them use files that are nearly 25,000 lines (when checking the number of lines in the saved file), and it only takes about 3 seconds to load all 8 tables.
|
|
|
|
Joined: Oct 2005
Posts: 1,741
Hoopy frood
|
Hoopy frood
Joined: Oct 2005
Posts: 1,741 |
Hopefully, you are already using the /hsave and /hload commands to save and load your hash table. Either way, you should be saving and loading the hash table using commands like this:
/hload myhash myhash.htb
/hsave myhash myhash.htb
-genius_at_work
|
|
|
|
Joined: May 2008
Posts: 127
Vogon poet
|
OP
Vogon poet
Joined: May 2008
Posts: 127 |
I'm saving to and loading from INI files (and yes, I'm specifying the INI section name).
So don't use INI's? Any extension I choose will work?
|
|
|
|
|