Refering to Variable Names with other Variables

BlitzMax Forums/BlitzMax Beginners Area/Refering to Variable Names with other Variables

Hey, I'm currently trying the B-Max demo, and was wondering if it could do something I never found a way to do in B+

Often in my games engine (which I want to port over to B-Max), the engine needs to refer to a variable, the name of which is contained within another variable.

Right now, I have to have a huge long list such as:

Select Thing
   Case "Thing1"
      DrawImage Thing1, X, Y
   Case "Thing2"
      DrawImage Thing2, X, Y
   Case "Thing3"
      DrawImage Thing3, X, Y
End Select


There is one specific procedure where the lists like this are getting absurdly long, where I read a map for a room (Made of 4 letter codes) from a file and have the images for the tiles they refer to in variables of the same name.

Is there a command in BlitzMax that would allow me to do something like this

DrawImage VarName(Thing), X, Y

Where the VarName command would take the string contained in Thing and refer to the variable of that name

I know it's not exactly what you're looking for, but couldn't you drop all of your "Things" into an array, and read an integer from the map that corresponds with the element of the array that you want?

DrawImage Things(thingInt), X, Y

Thats an interesting solution that I hadnt thought of

I'll experiment with this when I get to porting that part of the code (will be soon)

Thanks

Could use a hash table I should think.

Whats a hash table?

Here is another solution for you.

Strict 

Type TMedia Extends TMap
	Method DrawImage(Name:String, X:Float, Y:Float, Frame:Int=0)
		Local Obj:Object = ValueForKey(name)
		
		If Not Obj
			Return 
		EndIf
	
		brl.max2d.DrawImage(TImage(Obj), X, Y, Frame)
	End Method
End Type



Graphics(800,600,0)

Local YourImages:TMedia = New TMedia


For Local i:Int = 0 Until 5
	Local TempImageObj:TImage
	
	Cls
	
	SetScale 1.0, 1.0
	SetColor Rand(100,255), Rand(100,255), Rand(100,255)
	DrawRect 0,0,128,128
	
	SetColor 20,20,20
	SetScale 2.0, 2.0
	DrawText "thing"+i, 10,10	
	
	TempImageObj = CreateImage(128, 128)
	GrabImage(TempImageObj,0,0)
	
	YourImages.Insert("thing"+i, TempImageObj)
Next 



Cls

SetScale 1.0, 1.0
SetColor 255, 255, 255

For Local i:Int = 0 Until 5
	YourImages.DrawImage("thing"+i, i*128, 100)
Next 

Flip
WaitKey()


Mfg Suco

This is a hash table (original written by Matt Laurenson [Defoc8], I added a lot of stuff to it):
Rem
    HASHTABLE OBJECT
    
    - M.Laurenson/Defoc8 2006
EndRem

Rem
    Defoc would appreciate if you credited him if you
    used this.  No credit is to go to me.
    
    -Noel
EndRem

SuperStrict

Import Brl.Blitz
Import Brl.LinkedList
Import "hash.c"

Extern "C"
    Function __c_hash:Int(key$z, length:Int, initval:Int)="Hash"
EndExtern

Global HashKey:Int(name:String) = gHashKey

Function gHashKey%( name:String )
    Local i:Int = __c_hash( name, name.Length, 0 )
    If i = 0 Then Return 1 ' Unfortunately, I cannot allow the use of a zero key
    Return i
End Function

Type gHashTable
    'private:

    Field table:TList[]
    
    'protected:
    
    Method _rement(key:Int)
        For Local entry:gHashEntry = EachIn table[key Shr 24]
            If entry.key = key
                entry.node.Remove( )
                Return
            EndIf
        Next
    End Method
    
    Method _addent(key:Int,n:String,o:Object)
        Local entry:gHashEntry = New gHashEntry
        entry.key = key
        entry.obj = o 
        entry.name = n
        entry.node = table[key Shr 24].AddLast(entry)
    End Method
    
    Method _getent:Object(key:Int)
        If table[key Shr 24].Count( ) = 0 Then Return Null
        For Local entry:gHashEntry = EachIn table[key Shr 24]
            If entry.key = key
                Return entry.obj
            EndIf
        Next
        Return Null
    End Method
    
    'public:
    
    Method New()
        table=New TList[256]
        For Local n:Int = 0 To 255
            table[n] = New TList
        Next
    End Method
    
    Method SetEntry( name:String, obj:Object )
        Local key:Int = HashKey( name )
        Local o:Object = _getent(key)
        If o And o <> obj Then _rement(key)
        _addent(key,name,obj)
    End Method
    
    Method InsertEntry(name:String,obj:Object)
        Local key:Int = HashKey( name )
        _addent(key,name,obj)
    End Method
    
    Method RemoveEntry(name:String)
        Local key:Int = HashKey( name )
        _rement(key)
    End Method
    
    Method GetEntry:Object(name:String)
        Local key:Int = HashKey( name )
        Return _getent(key)
    End Method
    
    Method Flush()
        For Local n:Int = 0 To 255
            table[n].Clear( )
        Next
    End Method 
    
    Method GetEntryCount%(index:Int)
        Return table[index].Count( )
    End Method
    
    Method ToArray:Object[]( iters%=0 )
        Local amnt%=0
        For Local i:Int = 0 To 255
            amnt :+ table[i].Count( )
        Next
        Local obj:Object[amnt]
        Local c%=0
        For Local i:Int = 0 To 255
            For Local n:gHashEntry = EachIn table[i]
                If iters > 0 Then
                obj[c] = n
                Else
                obj[c] = n.obj
                EndIf
                c:+1
            Next
        Next
        Return obj
    End Method
    
    Method ObjectEnumerator:gHashTableEnum( )
        Local e:gHashTableEnum = New gHashTableEnum
        e.arr = ToArray( )
        Return e
    End Method
End Type

Type gHashTableEnum
    Field idx%=0
    Field arr:Object[]
    
    Method HasNext%( )
        If idx < arr.Length Then Return True
        Return False
    End Method
    
    Method NextObject:Object( )
        idx :+ 1
        Return arr[idx-1]
    End Method
End Type

Type gHashEntry
    Field key:Int
    Field name:String
    Field obj:Object
    Field node:TLink
End Type


This is hash.c:
/*
    Code by Bob Jenkins, December 1996, Public Domain.  Plus some minor,
    minor tweaks by Noel Cower.  Nothing that affects the implementation.
    You can use this free for any purpose.  It has no warranty.
*/

#define mix(a,b,c) \
{ \
  a -= b; a -= c; a ^= (c>>13); \
  b -= c; b -= a; b ^= (a<<8); \
  c -= a; c -= b; c ^= (b>>13); \
  a -= b; a -= c; a ^= (c>>12);  \
  b -= c; b -= a; b ^= (a<<16); \
  c -= a; c -= b; c ^= (b>>5); \
  a -= b; a -= c; a ^= (c>>3);  \
  b -= c; b -= a; b ^= (a<<10); \
  c -= a; c -= b; c ^= (b>>15); \
}

int Hash( k, length, initval )
register const unsigned char *k;        /* the key */
register int  length;   /* the length of the key */
register int  initval;    /* the previous hash, or an arbitrary value */
{
   register int a,b,c,len;

   /* Set up the internal state */
   len = length;
   a = b = 0x9e3779b9;  /* the golden ratio; an arbitrary value */
   c = initval;           /* the previous hash value */

   /*---------------------------------------- handle most of the key */
   while (len >= 12)
   {
      a += (k[0] +((int)k[1]<<8) +((int)k[2]<<16) +((int)k[3]<<24));
      b += (k[4] +((int)k[5]<<8) +((int)k[6]<<16) +((int)k[7]<<24));
      c += (k[8] +((int)k[9]<<8) +((int)k[10]<<16)+((int)k[11]<<24));
      mix(a,b,c);
      k += 12; len -= 12;
   }

   /*------------------------------------- handle the last 11 bytes */
   c += length;
   switch(len)              /* all the case statements fall through */
   {
   case 11: c+=((int)k[10]<<24);
   case 10: c+=((int)k[9]<<16);
   case 9 : c+=((int)k[8]<<8);
      /* the first byte of c is reserved for the length */
   case 8 : b+=((int)k[7]<<24);
   case 7 : b+=((int)k[6]<<16);
   case 6 : b+=((int)k[5]<<8);
   case 5 : b+=k[4];
   case 4 : a+=((int)k[3]<<24);
   case 3 : a+=((int)k[2]<<16);
   case 2 : a+=((int)k[1]<<8);
   case 1 : a+=k[0];
     /* case 0: nothing left to add */
   }
   mix(a,b,c);
   /*-------------------------------------------- report the result */
   return c;
}


This is how I use it in my engine:
Private
Global _objtable:gHashTable = New gHashTable
Public

' Returns handle/key
' Standard convention for registering objects in Indigo is that you name them like "Class::Name", or "Class::SubClass::Name"
' For objects that may have duplicate names, you should have a) an index and b) append the date, time, and millisecs to the Name section
Function RegisterObject%( name$, obj:Object )
    Local key:Int = gHashKey( name )
    Assert _objtable._getent( key )=Null,"Object with name ~q"+name+"~q already exists"
    _objtable._addent( key, name, obj )
    Return key
End Function

Function GetObjectByHandle:Object( handle% )
    Return _objtable._getent( handle )
End Function

Function GetObjectByName:Object( name$ )
    Local key:Int = gHashKey( name )
    Return _objtable._getent( key )
End Function

' If you want to avoid rehashing the key, pass Null/"" as name and pass the key to handle
Function UnregisterObject( name$, handle%=0 )
    ' Since BlitzMax only checks name.Length=0 if name <> Null, the following is actually safe
    If name <> Null And name.Length > 0 Then
        handle = gHashKey( name )
    EndIf
    _objtable._rement( handle )
End Function

Function GetGlobalObjectTable:gHashTable( )
    Return _objtable
End Function

Function FlushObjectTable( )
    _objtable.Flush( )
End Function


As pointed out by Suco-X, a map would work as well, but I think in this case a hash table would be faster.

Sorry Noel, but I'm afraid I dont understand any of that in the slightest

Suco, that method looks good, but it'll take some looking at before I undertand it completely, I'm very bad at understanding other peoples code, thanks for the help

Basically, RegisterObject registers an object in the global object table with the name specified by name$ and with the value obj:Object. It returns the key for this object (in the case that you wish to refer to it by key and not name).

You then use the key or name with either GetObjectByHandle or GetObjectByName to retrieve the object.

When you no longer want the object in the hash table, you unregister it with UnregisterObject and either pass the name string or specify the name as Null and pass the key to the second argument.

So if your type has a ToString method, you can use that as the name and retrieve whatever is stored with its name with GetObjectByName.

E.g., DrawImage( TImage(GetObjectByName( Thing.ToString( ) )), blahdee, blah, blah, fooblah )

Hopefully that made sense.

And I would recommend using the Indigo code (last codebox) since it's easier than having to know how to use the gHashTable type (not that it's hard, but having a simple public interface is nice).

The only 'problem', and this should be fairly obvious, is that your names must be unique. This is a trait shared by arrays as well. Two values can't share the same index, after all.

First off, thanks to Noel for posting that!
I'm really surprised that this thread disappeared. It is a great example, and precisely what I was looking for :)

I am looking for which is faster (A hash table or a TMap?) for getting a variable:Object that is linked to a key:String.
Comparing strings is a pretty painful thing, but it seems that hash tables are doing some similarly painful math stuff. Is there a specific difference in how each should be used?
In my case there aren't too many variables bouncing around (20 at most, I'd wager).

What is the grand purpose of hash tables? Would it be misuse to be using a hash table simply to link strings to objects, or is that the reason why they exist?


Edit

This thread is also pretty helpful: http://www.blitzbasic.com/Community/posts.php?topic=51627

A hashtable should be faster than a TMap once the TMap gets to some size - although it's hard to really tell without doing some testing.

A proper hash algorithm is very very fast. Once the hash itself is generated, the hash table is essentially just looking up the data in an array. So hash tables are only marginally slower than arrays. My hashtable, for instance, only spends 141 millisecs performing 900,000 lookups (I managed to get it to be faster than Lua's hash table lookups - woot!) Actually, these aren't just 900,000 hashes - it's actually retrieving an entry for a specific key 900,000 times, and then making an assignment to that value. So yeah, it's fast.


In your case, I think the hash table will be faster than the TMap. Given that you know approximately how many key/value pairs you'll need, you won't even face the one slow operation of a hash table - resizing it.

Should I post my hash code, even though it's not fully tidied up yet?

It's based off of the core code above but with plenty more functions. For instance, you can store multiple values per key with the ability to sort those via overloading the Compare method of the objects (this was so I could build ordered lists of key/value pairs that shared the same key...I was building a sort of "dynamic function" hooking system and the hooks could either exist or not, but if they did exist they needed to be ordered in a specific manner.)

Of course you can also "GetMultipleValues", when you've got more than one key/value pair that share the same key - you can specify the initial size of the table, you can manually grow the table (I planned to make this growth configureable to be automatic based on some variables that you set but haven't got there yet and probably won't be adding it, although it should be a relatively trivial add)

I also dug up a hash algorithm that is MUCH more efficient at evenly spreading the entries throughout the hash table.

Function pointers

141 millisecs performing 900,000 lookups

Holy... wow!
I keep forgetting how fast these computer things are :)

Thank you for the very in-depth information, SculptureOfSoul. It is very helpful!

(I'm also very glad to finally have some clue what those weird hash tables I keep hearing about actually do)

Well, I'm going to tidy up the code a bit and post it later. I'm not using it right now and if someone else finds some use for it all the better.

Regarding the speed: hehe, yeah, it's amazing what a computer can do. It's also amazing what kind of impact little optimizations can make. Hand-rolling a for loop instead of calling for-eachin shaved off ~200 or so millisecs from the above # of operations. While that's really rather trivial in the scheme of things, I was going to be doing tons of lookups per frame (every character in the MUD was a big hash-table of data, basically, so that I could add variables, add hooks to "logic modules", etc etc.) so I needed the utmost speed. I used Lua as a benchmark since it's fundamental variable type, the table, is based on hash tables and is highly optimized.

Hope to have the code up and in the archives within a few hours.

Dangit - I had a huge lengthy reply about the inner workings of a hash table and IE borked on me. Grrrrr.

Well, here's a link to the hashtable type I was talking about: http://www.blitzmax.com/codearcs/codearcs.php?code=1907

If you've got any questions, feel free to ask.