|
ReedN Wizard
Joined: 04 Jan 2006 Posts: 1279 Location: Portland, Oregon
|
Posted: Sun Mar 08, 2009 4:54 am
What's the best way to use a temp var with #delitem? |
I can't use just $one alone or it doesn't work. I ended up putting $one in a %concat statement. Is there any better way to do this?
#additem onevar "One"
#forall (one) {
$one = %concat( %i, "var")
#if (%ismember( "One", @{$one})) {
#say here $one
#delitem %concat( $one) "One"
}
}
#say "Final: " @onevar |
|
|
|
Vijilante SubAdmin
Joined: 18 Nov 2001 Posts: 5182
|
Posted: Sun Mar 08, 2009 9:40 am |
That is about the best way. Indirect references/pointers aren't really encouraged in zScript. There are usually better ways to build the same script result.
|
|
_________________ The only good questions are the ones we have never answered before.
Search the Forums |
|
|
|
ReedN Wizard
Joined: 04 Jan 2006 Posts: 1279 Location: Portland, Oregon
|
Posted: Sun Mar 08, 2009 3:43 pm |
If they aren't encouraged, what's the way to get around that then? I have a list of variable names that need to be looped through and I either need to construct them by looping through them or list them all outside a loop. I thought I was being a bit more efficient doing it this way.
|
|
|
|
gamma_ray Magician
Joined: 17 Apr 2005 Posts: 496
|
Posted: Sun Mar 08, 2009 3:55 pm |
Code: |
#while (%ismember("your string here", $test)) {$test = %delitem("your string here",$test)} |
|
|
|
|
ReedN Wizard
Joined: 04 Jan 2006 Posts: 1279 Location: Portland, Oregon
|
Posted: Sun Mar 08, 2009 4:10 pm |
Thanks for that code gamma_ray, but what I'm trying to do is a bit different from what I posted above to ask the initial question. In the code below I'm looping through the different string lists that hold city names and I remove the name if it is found. As far as I can tell I either do it this way or I unroll the loop and do them all one at a time manually. I just thought this looked a bit neater and was easier to alter if needed instead of making the same alteration to each of the components individually if I unroll it.
Code: |
#forall @CityList {
$CityVar = %concat( %i, "Citizens")
#if (%i <> %1 and %ismember( @NameHonor, @{$CityVar})) {
#delitem %concat( $CityVar) @NameHonor
}
}
|
|
|
|
|
Vijilante SubAdmin
Joined: 18 Nov 2001 Posts: 5182
|
Posted: Sun Mar 08, 2009 6:09 pm |
I can't really suggest a better way for this particular piece of script without knowing some details about the NameHonor and Citizen* variables. I am assuming CityList is a complete list of all citizens in your city, but it makes little sense to me why you would loop through such a list testing for '!=%1' when %ismember provides an equivalent capabilty. I need to know more about what the variables contain and what you want to do to suggest the correct solution.
So far I don't see anything that actually requires an indirect reference. |
|
_________________ The only good questions are the ones we have never answered before.
Search the Forums |
|
|
|
ReedN Wizard
Joined: 04 Jan 2006 Posts: 1279 Location: Portland, Oregon
|
Posted: Sun Mar 08, 2009 9:34 pm |
Going into that code, @CityList contains a list of the cities in the world. Combine the text in @CityList with 'Citizens' becomes the name of the variable with all the citizens of that particular city, for example @CitynameCitizens. @NameHonor is the name of an individual who is under investigation at this point in time. Additionally, %1 is the city to which the person in @NameHonor belongs.
So, overall, as I loop through the different cities if they don't belong to the city and they are listed under the city they are not a part of, then remove them from that list. They might be erroneously listed under any of them, so I check them all. |
|
|
|
gamma_ray Magician
Joined: 17 Apr 2005 Posts: 496
|
Posted: Sun Mar 08, 2009 9:51 pm |
From reading it (and guessing) I think what it is, is like this:
@CityList // a list of all cities in game
@CityCitizens // a list of all citizens in City, referenced by @{$CityVar}
//and I think it's in some sort of "check information about a person" script, so
%i // the name of the city that the person is actually currently a member of
@NameHonor // the person in question
So, check which city a person is a member of right now. Go through all cities, if it's not the city that the person is a member of, check if they're recorded as having been a member, if so, remove their name from the list.
No better version at present, maybe after breakfast. |
|
|
|
ReedN Wizard
Joined: 04 Jan 2006 Posts: 1279 Location: Portland, Oregon
|
Posted: Sun Mar 08, 2009 10:03 pm |
gamma_ray wrote: |
From reading it (and guessing) I think what it is, is like this:
@CityList // a list of all cities in game
@CityCitizens // a list of all citizens in City, referenced by @{$CityVar}
//and I think it's in some sort of "check information about a person" script, so
%i // the name of the city in the current loop
$1 // The name of the city that the person is actually currently a member of
@NameHonor // the person in question
So, check which city a person is a member of right now. Go through all cities, if it's not the city that the person is a member of, check if they're recorded as having been a member, if so, remove their name from the list.
|
I went through and tweaked your description, but it was nearly all correct. |
|
|
|
Vijilante SubAdmin
Joined: 18 Nov 2001 Posts: 5182
|
Posted: Sun Mar 08, 2009 10:16 pm |
I will assume citizenship can only be to one city.
#ADDKEY Citizen {@NameHonor} {%1}
If you really need them listed by city.
Code: |
#LOOPDB Citizen {
$val=%val
#IF (%key=%1) {
#ADDITEM $val {@NameHonor}
} {
#DELITEM $val {@NameHonor}
}
#ADDKEY Citizen {%key} {$val}
} |
|
|
_________________ The only good questions are the ones we have never answered before.
Search the Forums |
|
|
|
ReedN Wizard
Joined: 04 Jan 2006 Posts: 1279 Location: Portland, Oregon
|
Posted: Sun Mar 08, 2009 10:35 pm |
Normally I'd go with a database variable, but in this case I use the citizenship variables to highlight players with:
Regex: \b(@cityCitizens)\b
#cw color
Done for each city. |
|
|
|
gamma_ray Magician
Joined: 17 Apr 2005 Posts: 496
|
Posted: Sun Mar 08, 2009 11:51 pm |
It wouldn't be a bad idea to keep it in a DB var and then use that to update your string lists. At least you can get rid of looping and simply remove from the old citizenship list and add to the new citizenship list. Also, since you won't have any duplicate items on your lists (ever), you can use %removeitem without a loop.
|
|
|
|
Fang Xianfu GURU
Joined: 26 Jan 2004 Posts: 5155 Location: United Kingdom
|
Posted: Mon Mar 09, 2009 12:18 am |
You probably don't want to use that regex, ReedN - I'm assuming that the list of citizens will be pretty big, and running a regex with tons and tons of items without making some kind of trie from it will be incredibly slow. I remember someone was doing something similar with City Enemies on Achaea and was having trouble with the match speed of the regex. I've been meaning to get around to writing some code that'll take a long list of names like that and build a patricia trie regex from it in the hope that that'll improve the speed, but I've been so busy recently :(
Anyway, back to the original problem - let me first make sure I understand the problem. You have a bunch of variables CityCitizens that list all the people in that city, and you want to check to see if your variables are accurate. You have a way of determining which city someone's from, and after you use it you want to check that that person is stored in the correct city. Is that right?
Firstly, I assume that the list of cities is constant or changes only very occasionally, in which case you probably want to hard-code them. Any time something has to be compiled before it can be run (like the dynamic varrefs you're creating as a result of @{blah}) will be slower than something that can be compiled once and then just executed (a @blah reference).
The specific reason that the code in the OP doesn't work is because #delitem is trying to perform the operation on the contents of the $one variable rather than on a variable whose name is contained in the $one variable. Using %string($one) to work on the variable named in $one rather than $one itself should be all you need to get that particular code working.
But I wonder if that's the best way to go about this problem. Is this an operation you perform often, or is it a batch-style thing where you'll update everyone at the same time each day? Because it might be good for you to create a reverse-table of people you've recorded, where keys are people's names and values are the city they're recorded as being part of. Then you can simply test to see if their city is correct using the reverse table. If it isn't, you delete them from the old city (which you know from the reverse table) and add them to the new one.
The trouble comes with keeping the forwards- and reverse-tables synced, and I was writing it I'd want to completely rebuild the reverse table before I started looping through my list of names just to be on the safe side. But if the list is short (or even only one name long) then rebuilding the reverse table each time will be unwieldy and you'll either have to make sure you update the reverse table at the same time you do the forwards one, or use a different solution. |
|
|
|
ReedN Wizard
Joined: 04 Jan 2006 Posts: 1279 Location: Portland, Oregon
|
Posted: Mon Mar 09, 2009 12:37 am |
Fang Xianfu wrote: |
You probably don't want to use that regex, ReedN - I'm assuming that the list of citizens will be pretty big, and running a regex with tons and tons of items without making some kind of trie from it will be incredibly slow. I remember someone was doing something similar with City Enemies on Achaea and was having trouble with the match speed of the regex. I've been meaning to get around to writing some code that'll take a long list of names like that and build a patricia trie regex from it in the hope that that'll improve the speed, but I've been so busy recently :(
|
That's exactly what I'm using it for. I've made some enhancements to it since it was originally conceived. I rigorously delete anyone off the list that looks in the least inactive and such. Doing this I have the total combined list contents at about 4000 names total. Which is a lot to be searching for every line, but I find the benefit of this so worth while that I gladly pay the cost. I haven't noticed it slow things down too too much, but I'm always on the prowl for ways to optimize this. I'd love to try to make a patricia tree with this, but I'm not confident I'd get it right... and this from a person who is a Computer Engineer and has has a fair share of CS classes.
Fang Xianfu wrote: |
Anyway, back to the original problem - let me first make sure I understand the problem. You have a bunch of variables CityCitizens that list all the people in that city, and you want to check to see if your variables are accurate. You have a way of determining which city someone's from, and after you use it you want to check that that person is stored in the correct city. Is that right?
|
You've nailed it right on every assumption you made.
Fang Xianfu wrote: |
Firstly, I assume that the list of cities is constant or changes only very occasionally, in which case you probably want to hard-code them. Any time something has to be compiled before it can be run (like the dynamic varrefs you're creating as a result of @{blah}) will be slower than something that can be compiled once and then just executed (a @blah reference).
|
Normally updating this is a one time event every week or so. With that model of usage I'm fine with having the code a little more readable and with the actual execution speed taking a tad longer. However, I'm trying to re-write it so that it happens a little more as I'm running by having the collection of names occur as they unfold in the game and to keep from constantly updating them I'll keep a DB variable with the name as the key and the date of last update as the value. I'll keep that list trim by hourly deleting people off the list that have been there for more than 10 days. This way I can keep a running update going that won't require 15 minutes to redo the whole list.
Fang Xianfu wrote: |
But I wonder if that's the best way to go about this problem. Is this an operation you perform often, or is it a batch-style thing where you'll update everyone at the same time each day? Because it might be good for you to create a reverse-table of people you've recorded, where keys are people's names and values are the city they're recorded as being part of. Then you can simply test to see if their city is correct using the reverse table. If it isn't, you delete them from the old city (which you know from the reverse table) and add them to the new one.
The trouble comes with keeping the forwards- and reverse-tables synced, and I was writing it I'd want to completely rebuild the reverse table before I started looping through my list of names just to be on the safe side. But if the list is short (or even only one name long) then rebuilding the reverse table each time will be unwieldy and you'll either have to make sure you update the reverse table at the same time you do the forwards one, or use a different solution. |
I thought of doing it that way, but since there are 4000 names I'd like to avoid keeping multiple data structures around that contain different incarnations of the data. I could generate it at the time then delete it afterwords, but it might be a wash between just doing it the way I do it now and the overhead that would be generated by creating and maintaining the extra list. |
|
|
|
Fang Xianfu GURU
Joined: 26 Jan 2004 Posts: 5155 Location: United Kingdom
|
Posted: Mon Mar 09, 2009 1:15 am |
Well, building the tree is the easy bit and I already have code to do that. The trouble is making a regex from it, which is more complex than you'd think - you need to create the regex bart? when bar and bart are in the tree, but foo(?:bar)? when foo and foobar are in the tree. Frankly, I'm about ready to say "sod it" and just use bar(?:t)?, it's less pretty but much easier to write.
At least this thread is working well for getting me off my chuff and thinking about it :P |
|
|
|
Arde Enchanter
Joined: 09 Sep 2007 Posts: 605
|
Posted: Mon Mar 09, 2009 8:34 am |
Building a regex from the tree is a way easy than build the tree itself. I could upload my patricia tree package to the library, if you are interested in it
|
|
|
|
Vijilante SubAdmin
Joined: 18 Nov 2001 Posts: 5182
|
Posted: Mon Mar 09, 2009 11:43 am |
Knowing what your usage of this is changes a lot of things. Execution time isn't taking a tad longer, it is extremely horrible. Right now everytime you do this '#delitem %concat( $one) "One"' one of those '\b(@cityCitizens)\b' triggers has to be recompiled. I will write up a more detailed solution when I get home from work.
|
|
_________________ The only good questions are the ones we have never answered before.
Search the Forums |
|
|
|
ReedN Wizard
Joined: 04 Jan 2006 Posts: 1279 Location: Portland, Oregon
|
Posted: Mon Mar 09, 2009 8:22 pm |
I thought up a different way to approach this. After seeing how easy it is to lock up Cmud if I accidentally have an empty string in between the \b's I put some more thought into this.
Previously I had 7 individual regex triggers looking for: \b(@var1)\b through \b(@var7)\b
Now I have just one regex looking for \b([A-Z][a-z]+)\b. After it matches it then has a switch to determine which list it is a part of:
Code: |
#switch (%ismember( %1, @Cityless)) {#if (%ismember( %1, @CityEnemies)) {#CW white,firebrick} {#CW white}}
(%ismember( %1, @Ashtan)) {#CW brown}
(%ismember( %1, @Shallam)) {#if (%ismember( %1, @CityEnemies)) {#CW blue,firebrick} {#CW blue,gray}}
(%ismember( %1, @Mhaldor)) {#if (%ismember( %1, @CityEnemies)) {#CW magenta,firebrick} {#CW magenta,gray}}
(%ismember( %1, @Hashan)) {#if (%ismember( %1, @CityEnemies)) {#CW yellow,firebrick} {#CW yellow}}
(%ismember( %1, @Eleusis)) {#if (%ismember( %1, @CityEnemies)) {#CW green,firebrick} {#CW green,gray}}
(%ismember( %1, @Cyrene)) {#if (%ismember( %1, @CityEnemies)) {#CW cyan,firebrick} {#CW cyan}}
|
I did several ctrl-q tests before and after the changes. It took between 4.047-4.469 seconds to complete the old method, and between 2.282-2.406 seconds to complete after the changes.
So if this is an accurate description of the speed differences then I just speed things up by nearly 100%. |
|
|
|
Fang Xianfu GURU
Joined: 26 Jan 2004 Posts: 5155 Location: United Kingdom
|
Posted: Mon Mar 09, 2009 8:55 pm |
ctrl+q is about matching speed, though, not processing time, and in the case of ctrl+q it's only about the match coming up false, not true. The new regex will obviously fail faster for ctrl+q, because the three places where \b doesn't fail (start of the string, before the space and after it) will be rejected in one check by [A-Z]. All 4000 entries from the old list would have to be processed individually in each of those three positions, hence the speed increase. In real text, where matches might come up very often but not be a person's name, doing all those %ismember checks may end up being slower than using a regex. I guess it depends on the ratio of false positives to real names.
|
|
|
|
Vijilante SubAdmin
Joined: 18 Nov 2001 Posts: 5182
|
Posted: Mon Mar 09, 2009 9:03 pm |
That is certainely better. You can make it even better using a trigger that is only active part of the time. Some events merit checking against the names list, like a person entering the zone or room, or when you are specifically check who/qwho. This trigger can be enabled with "#STATE ColorNames 1" and shuts itself off quite conviently.
Code: |
<?xml version="1.0" encoding="ISO-8859-1" ?>
<cmud>
<trigger name="ColorNames" type="Manual" param="1000" priority="100000" trigontrig="false" newline="false" copy="yes">
<trigger type="Duration" param="1000" case="true" repeat="true" regex="true" priority="100001">
<pattern>\b([A-Z][a-z]+)\b</pattern>
<value>#switch (%ismember(%1, @Cityless)) {#if (%ismember( %1, @CityEnemies)) {#CW white,firebrick} {#CW white}}
(%ismember(%1, @Ashtan)) {#CW brown}
(%ismember(%1, @Shallam)) {#if (%ismember( %1, @CityEnemies)) {#CW blue,firebrick} {#CW blue,gray}}
(%ismember(%1, @Mhaldor)) {#if (%ismember( %1, @CityEnemies)) {#CW magenta,firebrick} {#CW magenta,gray}}
(%ismember(%1, @Hashan)) {#if (%ismember( %1, @CityEnemies)) {#CW yellow,firebrick} {#CW yellow}}
(%ismember(%1, @Eleusis)) {#if (%ismember( %1, @CityEnemies)) {#CW green,firebrick} {#CW green,gray}}
(%ismember(%1, @Cyrene)) {#if (%ismember( %1, @CityEnemies)) {#CW cyan,firebrick} {#CW cyan}}
#SET ColorNames 1 0
</value>
</trigger>
</trigger>
</cmud> |
Note the really high priority is so that it will retest anything a normal trigger caught and wanted to enable this trigger for. It makes it a reparse acrossed the board for many of your triggers. You just have to have a care that future triggers end up in a lower priority sequence.
I will contemplate some of the capturing details as I have time. |
|
_________________ The only good questions are the ones we have never answered before.
Search the Forums |
|
|
|
ReedN Wizard
Joined: 04 Jan 2006 Posts: 1279 Location: Portland, Oregon
|
Posted: Mon Mar 09, 2009 10:15 pm |
Fang: I was considering exactly those factors:
Pro:
Tests will now only occur primarily at:
- The start of sentences where the first letter is capitalized
- Proper names.
Previously all 7 triggers were testing each and every word boundary in a sentence which is many many more checks.
It can never hang because of a %null item in the list
Con:
It takes a lot more time to fail/pass each time it makes a hit.
Overall I thought the pros outweighed the cons.
Vijilante: That's a good idea and I had toyed around with doing something like that, but I'm afraid that I'd need to keep it on for 90% of the text coming in anyway. I'd want it active on just about any random line to show up because it might be some attack or even something new I hadn't seen before. With my anticipated nearly always on methodology, creating triggers to turn it off only during the portion I know I won't need it might just push it over into consuming more computation resources. |
|
|
|
ReedN Wizard
Joined: 04 Jan 2006 Posts: 1279 Location: Portland, Oregon
|
Posted: Tue Mar 10, 2009 4:16 pm |
It just occurred to me that now that I'm just testing for "\b([A-Z][a-z]+)\b" I can just change the string lists into a single DB variable. I think I'm correct in stating that it will be faster to do one lookup than to do 7 %ismember functions to each of the string lists. So now it would just be:
CityMembershipDB will have keys of Names and Values of city names.
Code: |
Regex: "\b([A-Z][a-z]+)\b"
#if (%iskey(%1, @CityMembershipDB)) {
$city_color = %db(%db(%1, @CityMembershipDB), @CityColorDB)
#if (%ismember(%1,@CityEnemies)) {#cw $city_color,firebrick} {#cw $city_color}
} |
I've already seen that doing a single search for "\b([A-Z][a-z]+)\b" makes the response of the system much snappier. So I think I'm on the right track here. |
|
|
|
Fang Xianfu GURU
Joined: 26 Jan 2004 Posts: 5155 Location: United Kingdom
|
Posted: Tue Mar 10, 2009 4:45 pm |
I would expect that to be faster, yes (though all seven %ismembers wouldn't be called every time, the average is probably still about 3.5, which is still going to be slow). You could even cut a call out by doing:
Code: |
$city=%db(%1, @CityMembershipDB)
#if ($city) {
$city_color = %db($city, @CityColorDB)
#if (%ismember(%1,@CityEnemies)) {#cw $city_color,firebrick} {#cw $city_color}
} |
But whether or not that's faster is something you'll have to check :P |
|
|
|
ReedN Wizard
Joined: 04 Jan 2006 Posts: 1279 Location: Portland, Oregon
|
Posted: Wed Mar 11, 2009 2:22 am |
I used your simplification Fang, thanks for the suggestion!
With the list converted to a DB variable I ran across this function and converted it. It takes a list of random people and displays them by their city grouping. I couldn't find any more efficient way to do this. Does this look optimal for what I'm trying to do?
Code: |
#local $shallam $hashan $ashtan $cyrene $eleusis $mhaldor $cityless $gods $unknown
#forall %1 {
//
#switch (%db( @Citizenships, %i) == "Ashtan") {#additem $ashtan %i}
(%db( @Citizenships, %i) == "Cityless") {#additem $cityless %i}
(%db( @Citizenships, %i) == "Cyrene") {#additem $cyrene %i}
(%db( @Citizenships, %i) == "Shallam") {#additem $shallam %i}
(%db( @Citizenships, %i) == "Hashan") {#additem $hashan %i}
(%db( @Citizenships, %i) == "Eleusis") {#additem $eleusis %i}
(%db( @Citizenships, %i) == "Mhaldor") {#additem $mhaldor %i}
(%db( @Citizenships, %i) == "Gods") {#additem $gods %i}
{#additem $unknown %i}
}
#if (%numitems( $ashtan)) {#say %ansi( white)[Ashtan]: %replace( $ashtan, "|", ", ")}
#if (%numitems( $cyrene)) {#say %ansi( white)[Cyrene]: %replace( $cyrene, "|", ", ")}
#if (%numitems( $eleusis)) {#say %ansi( white)[Eleusis]: %replace( $eleusis, "|", ", ")}
#if (%numitems( $hashan)) {#say %ansi( white)[Hashan]: %replace( $hashan, "|", ", ")}
#if (%numitems( $mhaldor)) {#say %ansi( white)[Mhaldor]: %replace( $mhaldor, "|", ", ")}
#if (%numitems( $shallam)) {#say %ansi( white)[Shallam]: %replace( $shallam, "|", ", ")}
#if (%numitems( $cityless)) {#say %ansi( white)[Cityless]: %replace( $cityless, "|", ", ")}
#if (%numitems( $gods)) {#say %ansi( white)[Gods]:%ansi( gray) %replace( $gods, "|", ", ")}
#if (%numitems( $unknown)) {#say %ansi( white)[Unknown]:%ansi( gray) %replace( $unknown, "|", ", ")}
|
|
|
|
|
Vijilante SubAdmin
Joined: 18 Nov 2001 Posts: 5182
|
Posted: Wed Mar 11, 2009 8:12 am |
Small optimization. Making the %db lookup occur only once per person will be slightly faster. The total savings will only be noticeable if your list has a fairly large number of people. If you are using this to process large lists frequently then I might suggest a fancy subregex.
Code: |
#forall %1 {
$city=%db( @Citizenships, %i)
#switch ($city) ("Ashtan") {#additem $ashtan %i}
("Cityless") {#additem $cityless %i}
("Cyrene") {#additem $cyrene %i}
("Shallam") {#additem $shallam %i}
("Hashan") {#additem $hashan %i}
("Eleusis") {#additem $eleusis %i}
("Mhaldor") {#additem $mhaldor %i}
("Gods") {#additem $gods %i}
{#additem $unknown %i} |
|
|
_________________ The only good questions are the ones we have never answered before.
Search the Forums |
|
|
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|