Snippet Dictionary (in zinc)

weaaddar

New Member
Reaction score
6
I'm not sure if it will optimize as to how you say. And if you run in debug mode it'll just break as it'll turn into a method call, which won't be an assignment.
 

SerraAvenger

Cuz I can
Reaction score
234
I'm not sure why the check for first is needed at all oO
Enumerators should be multithreading friendly. I'm not sure they are right now...
Perhaps we don't want to iterate through the whole thingie. Damiens right here.
 

weaaddar

New Member
Reaction score
6
And you can always invoke the destroy method any time you want. But fine I'm making some small fixes and I'll remove the auto-destroy.

As for the first thing its because the enumerator starts at the beginning of the dict, and if you go while(enumerator.MoveNext()), well that means your reading the second element in the body. I could probably make it so that The first call to moveNext doesn't do anything. Its thread-safe in the sense that Jass is a single threaded language. If you sleep and muck up the dictionary and delete the element the enumerator's looking at I'd throw an exception, but I can't. So when you moveNext again, the enumerator will quit (as the remove op broke the links the node had).
 

weaaddar

New Member
Reaction score
6
Somewhat agreed. But I don't like the notion of the internal data structure being expose and messed with. So a read-only view seems to be a compromise or otherwise you'd use Table and be done with it.
 

Jesus4Lyf

Good Idea™
Reaction score
397
>So a read-only view seems to be a compromise or otherwise you'd use Table and be done with it.
Make a typecasted thingie that looks like a normal node?
I dunno, ey. I hate the idea of iterators in JASS 'cos they don't inline (no "next" method will ever quite do it for me). No doubt I wouldn't use this to iterate.
 

weaaddar

New Member
Reaction score
6
Unfortunately the only compromise is basically this::
JASS:
public struct DictEnumerator_$T$[]
    {
        method operator Key()->$T$
        {
            return Node_$T$(this).Key;
        }
        method operator Value()->integer
        {
            return Node_$T$(this).Value;
        }
        method HasNext()->boolean
        {
            return Node_$T$(this).Next == 0;

        }
        method Next()->thistype
        {
            return Node_$T$(this).Next;
        }
    }

Which inlines as you'd expect, no longer requires construction, but now you have to do enumerator = enumerator.Next.
 

Jesus4Lyf

Good Idea™
Reaction score
397
>enumerator = enumerator.Next.
Which makes no sense. I'd call it a "node" or something, not an enumerator...
JASS:
        method HasNext()->boolean
        {
            return Node_$T$(this).Next == 0; // This is wrong, should be != 0.

        }

Not a fan. I'd use:
JASS:
        method IsFinished()->boolean
        {
            return this == 0;
        }

2 reasons. I'm using it in an exitwhen, and [LJASS]exitwhen not Node_$T$(this).Next != 0[/LJASS] is a double negative, and also it's an extra array read for no reason.

Nearly time to review your linked list structure, I spy [LJASS]==0[/LJASS]... means this can be optimised, I think...

Edit: Right.
JASS:
//.
        private method Fetch($T$ key)->Node_$T$
        {
            return LoadInteger(Table,integer(this),$HashFunc$(key));
        }

This is too low in your struct. In vJass, that would be called with a trigger evaluate. Is that the same for Zinc? Major optimisation just for moving it.
JASS:
//.
        method Add($T$ key,integer value)->boolean
        {
            Node_$T$ node;
            if(m_count > 0 && Contains(key)) return false; // m_count > 0 is the common case, I wouldn't have this check...
            node = Node_$T$.create(key,value);
            if(m_count > 0)
            {
                m_first.Prev = node;
                node.Next = m_first;
            }
            m_first = node;
            m_count+=1;
            SaveInteger(Table,integer(this),$HashFunc$(key),node);
            return true;
        }

(See comment above.)
JASS:
//.
            if(m_count > 0)
            {
                m_first.Prev = node;
                node.Next = m_first;
            }
            m_first = node;

-->
Make Dict extend Node...
JASS:
//.
            Prev.Next = node; // no first check, no extra first array thing
            node.Prev=Prev
            node.Next=this
            Prev=node

And then...
JASS:
//.
            Node_$T$ node;
            integer retVal = 0;
            if(key == m_first.Key) // first element.
            {
                RemoveSavedInteger(Table,integer(this),$HashFunc$(key),m_first);
                retVal = m_first.Value;
                node = m_first;
                m_first = m_first.Next;
                node.destroy();
                m_count-=1;
                return retVal;
            }
            node = Fetch(key);
            if(node == 0) return retVal;
            RemoveSavedInteger(Table,integer(this),$HashFunc$(key),node);
            retVal = node.Value;
            node.Prev.Next = node.Next;
            node.Next.Prev = node.Prev;
            node.destroy();
            //m_count-=1; // <-- this line is missing, your count seems to be broken.

-->
JASS:
//.
            Node_$T$ node = Fetch(key);
            integer retVal = node.Value; // if the node is 0, the value of an array at slot 0 is 0 anyway.
            if(retVal != 0)
            {
                RemoveSavedInteger(Table,integer(this),$HashFunc$(key),node);
                node.Prev.Next = node.Next;
                node.Next.Prev = node.Prev;
                node.destroy();
                m_count-=1;
            }
            return retVal;

Feel the magic.
Now instead of exitwhen this==0, you need to use exitwhen this==dict (single var read). But your add/remove logic is cleaner and faster. :p
 

SerraAvenger

Cuz I can
Reaction score
234
>enumerator = enumerator.Next.

enumerator.Next = X // Gets the xth element

could be funny syntax since enum.x= value can change enum in place : )
 

weaaddar

New Member
Reaction score
6
Yeah its horrible syntax. As for the == 0 thing that was a mistake as I quickly jiust wrote the method.

Also zinc doesn't support exitwhen, so you only are left with the fact that vex will probably inline and optimize the method.
 

Jesus4Lyf

Good Idea™
Reaction score
397
Sorry about the edit, not sure if you saw the suggestions:
JASS:
//.
        private method Fetch($T$ key)->Node_$T$
        {
            return LoadInteger(Table,integer(this),$HashFunc$(key));
        }

This is too low in your struct. In vJass, that would be called with a trigger evaluate. Is that the same for Zinc? Major optimisation just for moving it (the inline engine screws up if you have something try to inline through two func/method calls and an evaluate).
JASS:
//.
        method Add($T$ key,integer value)->boolean
        {
            Node_$T$ node;
            if(m_count > 0 && Contains(key)) return false; // m_count > 0 is the common case, I wouldn't have this check...
            node = Node_$T$.create(key,value);
            if(m_count > 0)
            {
                m_first.Prev = node;
                node.Next = m_first;
            }
            m_first = node;
            m_count+=1;
            SaveInteger(Table,integer(this),$HashFunc$(key),node);
            return true;
        }

(See comment above.)
JASS:
//.
            if(m_count > 0)
            {
                m_first.Prev = node;
                node.Next = m_first;
            }
            m_first = node;

-->
Make Dict extend Node...
JASS:
//.
            Prev.Next = node; // no first check, no extra first array thing
            node.Prev=Prev
            node.Next=this
            Prev=node

And then...
JASS:
//.
            Node_$T$ node;
            integer retVal = 0;
            if(key == m_first.Key) // first element.
            {
                RemoveSavedInteger(Table,integer(this),$HashFunc$(key),m_first);
                retVal = m_first.Value;
                node = m_first;
                m_first = m_first.Next;
                node.destroy();
                m_count-=1;
                return retVal;
            }
            node = Fetch(key);
            if(node == 0) return retVal;
            RemoveSavedInteger(Table,integer(this),$HashFunc$(key),node);
            retVal = node.Value;
            node.Prev.Next = node.Next;
            node.Next.Prev = node.Prev;
            node.destroy();
            //m_count-=1; // <-- this line is missing, your count seems to be broken.

-->
JASS:
//.
            Node_$T$ node = Fetch(key);
            integer retVal = node.Value; // if the node is 0, the value of an array at slot 0 is 0 anyway.
            if(retVal != 0)
            {
                RemoveSavedInteger(Table,integer(this),$HashFunc$(key),node);
                node.Prev.Next = node.Next;
                node.Next.Prev = node.Prev;
                node.destroy();
                m_count-=1;
            }
            return retVal;

Feel the magic.
Now instead of exitwhen this==0, you need to use exitwhen this==dict (single var read). But your add/remove logic is cleaner and faster. :p
Bolded line added.
 

weaaddar

New Member
Reaction score
6
I read them over and they are very good suggestions! You are probably right that I need to make fetch the top most method. It seem simple enough to be inlined and if isn't then a lot of performance can be lost.

I'm not sure about making dict extend node. The only possible solution is to make node's variables private (right now they are public) and have a public accessor.

As for the add method suggestion, I still need to check if it already contains the node, circular lists are a bad thing in the world of a blind enumerator.
 

Jesus4Lyf

Good Idea™
Reaction score
397
>I read them over and they are very good suggestions!
Thanks! :)

>I'm not sure about making dict extend node. The only possible solution is to make node's variables private (right now they are public) and have a public accessor.

Premise of that is simple add/remove. Dict.prev becomes the last node, and Dict.next becomes the first. Means it is always 100% safe to set this.prev.next=this.next and this.next.prev=this.prev to slide a node out of a list. Also saves arrays.

>As for the add method suggestion, I still need to check if it already contains the node
Didn't say to remove that, just said change those few lines I highlighted...

>circular lists are a bad thing in the world of a blind enumerator.
I don't get it. :p
 

weaaddar

New Member
Reaction score
6
Yeah, I didn't understand what you were doing, but now it makes sense. Make the whole thing a big circular list. Well then there is only one wierd thing Dict.Key and Dict.Value are exposed and don't do anything.
 

Jesus4Lyf

Good Idea™
Reaction score
397
>Dict.Key and Dict.Value are exposed
Gah, yet another reason I wish there was a [LJASS]protected[/LJASS] keyword which was only accessible from child structs...

But there isn't. Do some typecast crap to remove their exposure, if you like. :thup:

PS. If you want to see an example of the concept in practise, check T32.
 

weaaddar

New Member
Reaction score
6
I updated the code and followed most of your suggestions.And I'm using what I feel is some horseshit typecasting. I wish Vex would implement the internal keyword. I also added Set to the main macro. I'm not sure if there is a way for static if to read macro parameters without doing something annoying like create a constant variable in the macro and referencing its value, so if you guys think its not cool to throw in the set I can probably just remove it.

The enumerator is gone, and in its place are readonly nodes. You can now use a new enumerator pattern to visit the elements. Its a bit wordy and kind of hokey (exitwhen node == dict), but the good new is it all inlines (Yay!), and they are array structs. So you don't have to destroy them. I feel this is a good compromise for people who are like me and want to basically set'em and forget'em. Also the for loop construct in zinc makes most of the pain go away. If Vex would go that nice extra mile and implement the comma "," operator for the for loop it'd be even cleaner.

Edit:
Since performance questions are bound to come-up. A linked list will beat a set when it comes to adding elements, tie when it comes to iterating through (with maybe a slight performance as you can just start from the first and go), and lose when it comes to removing or finding if it has elements.

Edit2:
Holy shit, tryGetValue is miserable.
JASS:
        if(dict.Contains(7))then
            set res = dict[7]
        endif

vs
JASS:
        if(dict.TryGetValue(7))then
            set res = dict.Value
        endif

The first method clocks at .18 the second at .30 for 5000 iterations. It makes sense as the first completely inlines to native code, but wow the performance penalty for using functions is pretty nuts. I was hoping the not using a hashtable would be worth its convoluted inclusion. This isn't such a bad thing as getting rid of TryGetValue and making .Count store its data in the value column.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top