Snippet IterableHashMap

krainert

Member
Reaction score
10
EDIT: Name has changed to LinkedHashMap.
In case anybody needs it:
http://krainert.com/content/linkedhashmap/
All methods except IterableHashMap.forEach, which should run in linear time relative to the amount of entries in the map, should run in constant time given that lookups in Blizz's hashtables can be done as such.
If you find any bugs, please do let me know. If you think this library is pointless, please let me know why, and I'll try to get back to it.

JASS:
library LinkedHashMap

//********************************************************************************************************************************
//* LinkedHashMap
//*  
//*  ABOUT
//*   Author:   krainert
//*   Version:  2.0
//*   
//*  DESCRIPTION
//*   This library provides linked hash maps with integer keys and values
//*   Note: Entries are linked in the order in which theirs keys were added beginning with the entry with the oldest key
//*   
//*  CLASSES
//*   ======== LinkedHashMap ========
//*    -- Description
//*     An linked hash map
//*    -- Operators
//*     [integer key]                  get(key)
//*     [integer key] = integer value  put(key, value)
//*    -- Methods
//*     static LinkedHashMap           create()                                          - returns a new linked hash map
//*     integer                        revision()                                        - returns the revision of this map
//*     integer                        size()                                            - returns the number of entries in this map
//*     boolean                        has(integer key)                                  - returns whether this map contains an entry with key 'key'
//*     integer                        get(integer key)                                  - returns the value of the entry with key 'key' in this map
//*     void                           put(integer key, integer value)                   - adds an entry with key 'key' and value 'value' to this map
//*     void                           remove(integer key)                               - removes the entry with key 'key' from this map
//*     void                           clear()                                           - removes all entries from this map
//*     boolean                        hasNext(integer key)                              - returns whether a successor for the entry with key 'key' exists in this map or whether this map contains any entries if 'key' is -1
//*     integer                        next(integer key)                                 - returns the key of the successor for the entry with key 'key' in this map or the key of the first entry in this map if 'key' is -1
//*     LinkedHashMapIterator          iterator()                                        - returns an iterator for this map
//*     boolean                        forEach(LinkedHashMapIterationHandler handler)    - for every entry in the map calls 'handler' with the value of the current entry as the argument for parameter value
//*    -- Debug mode exclusive methods
//*     void                           print()                                           - prints a summary of the current state of this map
//*   ======== LinkedHashMapIterator ========
//*    -- Description
//*     An linked hash map iterator
//*    -- Methods
//*     static LinkedHashMapIterator   create(LinkedHashMap map)                         - returns a new linked hash map iterator over an linked hash map 'map'
//*     boolean                        hasNext()                                         - returns whether the underlaying map contains any more entries to be iterated
//*     integer                        next()                                            - returns the value of the next entry in the underlaying map to be iterated
//*     
//*  FUNCTION INTERFACES
//*   boolean LinkedHashMapIterationHandler(integer value)                               - handles the entry with value 'value' and returns whether any current iteration of entries of which the call to this handler is part should be terminated subsequently
//*   
//*  CHANGELOG
//*   1.0  2011-11-13  Original release
//*   2.0  2011-11-14  Library renamed from IterableHashMap to LinkedHashMap
//*                    IterableHashMap renamed to LinkedHashMap
//*                    IterableHashMapIterator renamed to LinkedHashMapIterator
//*                    IterableHashMapIterationHandler renamed to LinkedHashMapIterationHandler
//*                    Fault related debug messages disabled outside of debug mode
//*                    Debug mode exclusive method print added to LinkedHashMap
//*   
//********************************************************************************************************************************

globals
    private constant integer KEY_START = -1              //key of a virtual entry preceeding the first in the map
    private constant integer KEY_MIN   = 0               //lowest actual key allowed
    private constant integer KEY_MAX   = 2147483647      //highest actual key allowed
    private constant integer KEY_ERROR = -1              //indicates inability to return a key
    private constant integer VAL_MIN   = 0               //lowest value allowed
    private constant integer VAL_MAX   = 2147483647      //highest value allowed
    private constant integer VAL_ERROR = -1              //indicates inability to return a value
    private constant integer REV_MIN   = -2147483648     //lowest possible revision
    private constant integer REV_MAX   = 2147483647      //highest possible revision
    private constant integer REV_SPAN  = REV_MAX-REV_MIN //max number of modifications to map allowed
    private constant integer VALUE     = 0               //index of values in the hashtable
    private constant integer PREV      = 1               //index of keys for preceeding entries in the hashtable
    private constant integer NEXT      = 2               //index of keys for succeeding entries in the hashtable
endglobals

function interface LinkedHashMapIterationHandler takes integer value returns boolean

struct LinkedHashMapIterator
    private integer rev
    private LinkedHashMap map
    private integer currentKey
    
    static if debug then
        static method msg takes string message returns nothing
            call BJDebugMsg("[LinkedHashMapIterator] " + message)
        endmethod
        static method err takes string message returns nothing
            call msg("ERROR: " + message)
        endmethod
    endif
    
    static method create takes LinkedHashMap map returns thistype
        local thistype r = .allocate()
        set r.rev = map.revision()
        set r.map = map
        set r.currentKey = KEY_START
        return r
    endmethod
    
    method hasNext takes nothing returns boolean
        static if debug then
            if (.rev != .map.revision()) then
                call err("hasNext(): revision mismatch; iterator out of sync with underlying map")
                return false
            endif
        endif
        return .map.hasNext(.currentKey)
    endmethod
    
    method next takes nothing returns integer
        static if debug then
            if (.rev != .map.revision()) then
                call err("next(): revision mismatch; iterator out of sync with underlying map")
                return VAL_ERROR
            endif
            if (not(.map.hasNext(.currentKey))) then
                call err("next(): no successor; underlying map does not contain any more entries")
                return VAL_ERROR
            endif
        endif
        set .currentKey = .map.next(.currentKey)
        return .map.get(.currentKey)
    endmethod
endstruct

struct LinkedHashMap
    private hashtable map = InitHashtable()
    private integer first
    private integer last
    private integer count = 0
    private integer rev = REV_MIN
    
    static if debug then
        static method msg takes string message returns nothing
            call BJDebugMsg("[LinkedHashMap] " + message)
        endmethod
        static method err takes string message returns nothing
            call msg("ERROR: " + message)
        endmethod
    endif
    
    method operator [] takes integer key returns integer
        return .get(key)
    endmethod
    
    method operator []= takes integer key, integer value returns nothing
        call .put(key, value)
    endmethod
    
    static method create takes nothing returns thistype
        return .allocate()
    endmethod
    
    method onDestroy takes nothing returns nothing
        call FlushParentHashtable(.map)
    endmethod
    
    method revision takes nothing returns integer
        return .rev
    endmethod
    
    method size takes nothing returns integer
        return .count
    endmethod
    
    method has takes integer key returns boolean
        static if debug then
            if (key < KEY_MIN or key > KEY_MAX) then
                call err("has(" + I2S(key) + "): invalid key; key must be in range [" + I2S(KEY_MIN) + "," + I2S(KEY_MAX) + "]")
                return false
            endif
        endif
        return HaveSavedInteger(.map, key, VALUE)
    endmethod
    
    method get takes integer key returns integer
        static if debug then
            if (key < KEY_MIN or key > KEY_MAX) then
                call err("get(" + I2S(key) + "): invalid key; key must be in range [" + I2S(KEY_MIN) + "," + I2S(KEY_MAX) + "]")
                return VAL_ERROR
            endif
            if (not(HaveSavedInteger(.map, key, VALUE))) then
                call err("get(" + I2S(key) + "): unrecognized key; map does not contain referenced entry")
                return VAL_ERROR
            endif
        endif
        return LoadInteger(.map, key, VALUE)
    endmethod
    
    method put takes integer key, integer value returns nothing
        static if debug then
            if (key < KEY_MIN or key > KEY_MAX) then
                call err("put(" + I2S(key) + "," + I2S(value) + "): invalid key; key must be in range [" + I2S(KEY_MIN) + "," + I2S(KEY_MAX) + "]")
                return
            endif
            if (value < VAL_MIN or value > VAL_MAX) then
                call err("put(" + I2S(key) + "," + I2S(value) + "): invalid value; value must be in range [" + I2S(VAL_MIN) + "," + I2S(VAL_MAX) + "]")
                return
            endif
            if (.rev == REV_MAX) then
                call err("put(" + I2S(key) + "," + I2S(value) + "): revision overflow; map cannot be modified more than " + I2S(REV_SPAN) + " times")
                return
            endif
        endif
        set .rev = .rev + 1
        if (.count == 0) then
            set .count = 1
            set .first = key
            set .last  = key
        elseif (not(HaveSavedInteger(.map, key, VALUE))) then
            set .count = .count + 1
            call SaveInteger(.map, .last, NEXT, key)
            call SaveInteger(.map, key, PREV, .last)
            set .last = key
        endif
        call SaveInteger(.map, key, VALUE, value)
    endmethod
    
    method remove takes integer key returns nothing
        static if debug then
            if (key < KEY_MIN or key > KEY_MAX) then
                call err("remove(" + I2S(key) + "): invalid key; key must be in range [" + I2S(KEY_MIN) + "," + I2S(KEY_MAX) + "]")
                return
            endif
            if (not(HaveSavedInteger(.map, key, VALUE))) then
                call err("remove(" + I2S(key) + "): unrecognized key; map does not contain referenced entry")
                return
            endif
            if (.rev == REV_MAX) then
                call err("remove(" + I2S(key) + "): revision overflow; map cannot be modified more than " + I2S(REV_SPAN) + " times")
                return
            endif
        endif
        set .rev = .rev + 1
        if (.count == 1) then
            set .count = 0
        else
            set .count = .count - 1
            if (key == .first) then
                call RemoveSavedInteger(.map, LoadInteger(.map, key, NEXT), PREV)
                set .first = LoadInteger(.map, key, NEXT)
            elseif (key == .last) then
                call RemoveSavedInteger(.map, LoadInteger(.map, key, PREV), NEXT)
                set .last = LoadInteger(.map, key, PREV)
            else
                call SaveInteger(.map, LoadInteger(.map, key, PREV), NEXT, LoadInteger(.map, key, NEXT))
                call SaveInteger(.map, LoadInteger(.map, key, NEXT), PREV, LoadInteger(.map, key, PREV))
            endif
        endif
        call FlushChildHashtable(.map, key)
    endmethod
    
    method clear takes nothing returns nothing
        static if debug then
            if (.rev == REV_MAX) then
                call err("clear(): revision overflow; map cannot be modified more than " + I2S(REV_SPAN) + " times")
                return
            endif
        endif
        set .rev = .rev + 1
        set .count = 0
        call FlushParentHashtable(.map)
        set .map = InitHashtable()
    endmethod
    
    method hasNext takes integer key returns boolean
        static if debug then
            if ((key < KEY_MIN or key > KEY_MAX) and key != KEY_START) then
                call err("hasNext(" + I2S(key) + "): invalid key; key must be in range [" + I2S(KEY_MIN) + "," + I2S(KEY_MAX) + "] or " + I2S(KEY_START))
                return false
            endif
        endif
        if (key == KEY_START) then
            return .count != 0
        endif
        static if debug then
            if (not(HaveSavedInteger(.map, key, VALUE))) then
                call err("hasNext(" + I2S(key) + "): unrecognized key; map does not contain referenced entry")
                return false
            endif
        endif
        return key != .last
    endmethod
    
    method next takes integer key returns integer
        static if debug then
            if ((key < KEY_MIN or key > KEY_MAX) and key != KEY_START) then
                call err("next(" + I2S(key) + "): invalid key; key must be in range [" + I2S(KEY_MIN) + "," + I2S(KEY_MAX) + "] or " + I2S(KEY_START))
                return KEY_ERROR
            endif
        endif
        if (key == KEY_START) then
            return .first
        endif
        static if debug then
            if (not(HaveSavedInteger(.map, key, VALUE))) then
                call err("next(" + I2S(key) + "): unrecognized key; map does not contain referenced entry")
                return KEY_ERROR
            endif
            if (key == .last) then
                call err("next(" + I2S(key) + "): no successor; map does not contain any more entries")
                return KEY_ERROR
            endif
        endif
        return LoadInteger(.map, key, NEXT)
    endmethod
    
    method iterator takes nothing returns LinkedHashMapIterator
        return LinkedHashMapIterator.create(this)
    endmethod
    
    method forEach takes LinkedHashMapIterationHandler handler returns boolean
        local integer rev = .rev
        local integer key
        if (.count != 0) then
            static if debug then
                if (rev != .rev) then
                    call err("forEach(...): revision mismatch; iteration out of sync with map")
                    return false
                endif
            endif
            set key = .first
            loop
                if handler.evaluate(LoadInteger(.map, key, VALUE)) then
                    return true
                endif
                exitwhen key == .last
                set key = LoadInteger(.map, key, NEXT)
            endloop
        endif
        return false
    endmethod
    
    static if debug then
      method print takes nothing returns nothing
          local integer key
          local integer value
          local integer prev
          local integer next
          call msg("LinkedHashMap")
          call msg("    .first = " + I2S(.first))
          call msg("    .last = " + I2S(.last))
          call msg("    .count = " + I2S(.count))
          call msg("    .rev = " + I2S(.rev))
          call msg("    entries:")
          if (.count != 0) then
              set key = .first
              loop
                  set value = LoadInteger(.map, key, VALUE)
                  if (HaveSavedInteger(.map, key, PREV)) then
                      set prev = LoadInteger(.map, key, PREV)
                  else
                      set prev = -2
                  endif
                  if (HaveSavedInteger(.map, key, NEXT)) then
                      set next = LoadInteger(.map, key, NEXT)
                  else
                      set next = -2
                  endif
                  call msg("        " + I2S(key) + " -> ( " + I2S(value) + " , " + I2S(prev) + " , " + I2S(next) + " )")
                  exitwhen key == .last
                  set key = next
              endloop
          endif
      endmethod
    endif
endstruct

endlibrary


See also:
LinkedList (by the one and only Bribe)
LinkedListModule (by Dirac)
 

Laiev

Hey Listen!!
Reaction score
188
Every [ljass]BJDebugMsg[/ljass] should only display on debug mode.

This is not C, rewrite your docs.
 

krainert

Member
Reaction score
10
Every [ljass]BJDebugMsg[/ljass] should only display on debug mode.
No [ljass]BJDebugMsg[/ljass] should ever be called if the programmer uses the code correctly. Wherefore exactly should I then omit them outside debug mode?
EDIT: To explicate my logic: Within my code, debug messages displayed outside of debug mode are to be considered as fault indicators. That is to say, had the implementation language been Java, I would've tossed RuntimeExceptions.

This is not C, rewrite your docs.
I find having a readable documentation syntax weight heavier than having one mimicking the syntax of the the implementation language. Should the user be unfamiliar with C-like syntax, he/she should be able to pick it up in a matter of minutes (or seconds).
 

Laiev

Hey Listen!!
Reaction score
188
I repeat, this is not C.

And again, Debug Msg should be displayed only in debug mode.

You're slowing down your functions checking ifs all the time, this is unnecessary and some function will not inline because of your 'logic'.
 

krainert

Member
Reaction score
10
I repeat, this is not C.
And again, Debug Msg should be displayed only in debug mode.
You're slowing down your functions checking ifs all the time, this is unnecessary and some function will not inline because of your 'logic'.
Good point! I seem to have overlooked concerns of efficiency. Version 1.1 disabling all debug messages outside of debug mode should be out sometime tomorrow.
 

Bribe

vJass errors are legion
Reaction score
67
Only that I can't think of any reason you'd want to use mine let alone yours over Dirac's LinkedListModule. His is much more speedy.

Although if someone can find a practical, non-theoretical reason to use the dynamic hash linked list type structure as I have and you seem to have here, I am all ears.
 

krainert

Member
Reaction score
10
Only that I can't think of any reason you'd want to use mine let alone yours over Dirac's LinkedListModule. His is much more speedy.
Although if someone can find a practical, non-theoretical reason to use the dynamic hash linked list type structure as I have and you seem to have here, I am all ears.
Hmm, yes, LLM is pretty clever. I personally prefer maintaining a rigid dichotomy between structure and elements, but apart from this mostly idealist or theoretical reason, I do believe you're right in that LLM might be (borderline) universally preferable. I'll add links for LinkedList and LLM in #1.
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • Varine Varine:
    How can you tell the difference between real traffic and indexing or AI generation bots?
  • The Helper The Helper:
    The bots will show up as users online in the forum software but they do not show up in my stats tracking. I am sure there are bots in the stats but the way alot of the bots treat the site do not show up on the stats
  • Varine Varine:
    I want to build a filtration system for my 3d printer, and that shit is so much more complicated than I thought it would be
  • Varine Varine:
    Apparently ABS emits styrene particulates which can be like .2 micrometers, which idk if the VOC detectors I have can even catch that
  • Varine Varine:
    Anyway I need to get some of those sensors and two air pressure sensors installed before an after the filters, which I need to figure out how to calculate the necessary pressure for and I have yet to find anything that tells me how to actually do that, just the cfm ratings
  • Varine Varine:
    And then I have to set up an arduino board to read those sensors, which I also don't know very much about but I have a whole bunch of crash course things for that
  • Varine Varine:
    These sensors are also a lot more than I thought they would be. Like 5 to 10 each, idk why but I assumed they would be like 2 dollars
  • Varine Varine:
    Another issue I'm learning is that a lot of the air quality sensors don't work at very high ambient temperatures. I'm planning on heating this enclosure to like 60C or so, and that's the upper limit of their functionality
  • Varine Varine:
    Although I don't know if I need to actually actively heat it or just let the plate and hotend bring the ambient temp to whatever it will, but even then I need to figure out an exfiltration for hot air. I think I kind of know what to do but it's still fucking confusing
  • The Helper The Helper:
    Maybe you could find some of that information from AC tech - like how they detect freon and such
  • Varine Varine:
    That's mostly what I've been looking at
  • Varine Varine:
    I don't think I'm dealing with quite the same pressures though, at the very least its a significantly smaller system. For the time being I'm just going to put together a quick scrubby box though and hope it works good enough to not make my house toxic
  • Varine Varine:
    I mean I don't use this enough to pose any significant danger I don't think, but I would still rather not be throwing styrene all over the air
  • The Helper The Helper:
    New dessert added to recipes Southern Pecan Praline Cake https://www.thehelper.net/threads/recipe-southern-pecan-praline-cake.193555/
  • The Helper The Helper:
    Another bot invasion 493 members online most of them bots that do not show up on stats
  • Varine Varine:
    I'm looking at a solid 378 guests, but 3 members. Of which two are me and VSNES. The third is unlisted, which makes me think its a ghost.
    +1
  • The Helper The Helper:
    Some members choose invisibility mode
    +1
  • The Helper The Helper:
    I bitch about Xenforo sometimes but it really is full featured you just have to really know what you are doing to get the most out of it.
  • The Helper The Helper:
    It is just not easy to fix styles and customize but it definitely can be done
  • The Helper The Helper:
    I do know this - xenforo dropped the ball by not keeping the vbulletin reputation comments as a feature. The loss of the Reputation comments data when we switched to Xenforo really was the death knell for the site when it came to all the users that left. I know I missed it so much and I got way less interested in the site when that feature was gone and I run the site.
  • Blackveiled Blackveiled:
    People love rep, lol
    +1
  • The Helper The Helper:
    The recipe today is Sloppy Joe Casserole - one of my faves LOL https://www.thehelper.net/threads/sloppy-joe-casserole-with-manwich.193585/
  • The Helper The Helper:
    Decided to put up a healthier type recipe to mix it up - Honey Garlic Shrimp Stir-Fry https://www.thehelper.net/threads/recipe-honey-garlic-shrimp-stir-fry.193595/

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top