Free Web Hosting Provider - Web Hosting - E-commerce - High Speed Internet - Free Web Page
Search the Web

vbProgramming | Tutorials | An Alternative to Arrays
    vbProgramming 
Tutorials |
An Alternative to Arrays
 
     

vbProgramming Home :: vbProgramming Forums :: Tutorials :: Contact :: Links 

 

 

Sick and tired of arrays? Meet Collections, Hashtables, ArrayLists, Queues and Stacks. They're all "Arrays" without bounds. They have a lot more features than arrays too.

When NOT to use Arrays
Before going around telling you why to use the array alternative. I should first tell you when (and why) NOT to use arrays.
Arrays are great for 'simple things'. Such as an array of booleans, an array of integers, an array of bitmaps, an array of strings. However when you start doing an array of classes, things start to get funky (try this yourself. Create a class and create an array of them and try to use some of their methods). When writing large programs, managing these arrays gets a little harder. For example, let's say you wanted to delete an element in an array.

Your array:
Dim myArray(3) as Integer
'Assign some values for all the elements in the array
myArray(0) = 0
myArray(1) = 1
myArray(2) = 2
myArray(3) = 3

Let's say you wanted to remove the 3rd element from the array. You'd have to do something like:
Dim tempArray(3) as Integer
tempArray = myArray 'Copy all the elements into temparray

ReDim myarray(2)

For x = 0 To 3
If x < 2 Then
myarray(x) = temparray(x)
ElseIf x > 2 Then
myarray(x - 1) = temparray(x)
End If
Next

For x = 0 To 2
MessageBox.Show(myarray(x))
Next

All this just to remove one element. Crazy huh?

So I'd recommend using the stuff we're going to talk about when:
a) You have to frequently change the size of the array
b) You frequently change the elements of the array
c) When it's something big (for small things, such as integers, you probably don't need to "Step Up" to collections)
d) When you store classes into an array (Classes don't work well with arrays.. well they do but it's a bit more confusing to use them in an Array)

Collections - A VB.NET Exclusive
The first thing we're going to examine is Collections. This can only be found in VB.NET (C# doesn't have it, J# doesn't have it, and I don't think C++.NET does either - I'm talking about managed (Managed means .NET basically) languages here, so Java could have this).

We'll start off with a basic class:
Public Class clsSprite
   Public Name As String
   Public Sub New(ByVal characterName As String)
       Name = characterName
   End Sub
End Class

As I said, fairly basic.

Now,  in form1:
 Dim c As New Collection

Next, in form1_Load, we're going to add a clsSprite to the collection.
c.Add(New clsSprite("Guy in Collection"))

Awesome. Now we've got a clsSprite. As of now, he's being stored in c.Items(1), because collections start at 1 (unlike Arrays which start at 0).
To messagebox the character's name property, you'd do this.

    MessageBox.Show(c.Item(1).Name)

Let's examine this step by step. I said earlier that Collections start at 1 <-- that's where our Sprite is stored. Our clsSprite has a .Name variable, and all it does is simply messageboxes it. Type the line above manually. You'll see that when you type c.Item(1). you won't get an Intellisense Box that says "Name". That's because you can put any type of object in collection c. It doesn't know that c.Item(1) is a clsSprite! Run it. It should tell you it's name. Now change the .Name to .Names. It won't give you an error of course, but when you run it you'll get:
Additional information: Public member 'Names' on type 'clsSprite' not found.

Get what I mean? There's no 'Names' in clsSprite. So in run-time, the compiler looks at that line of code and realizes that c.Item(1) is a clsSprite then looks for Names in clsSprite, and when it finds out there is no Names,
it returns an error. Change Names back to Name.

Usually, we use collections to store one type of object (albeit many of that type). In our case, pretend c is only for clsSprite. When the compiler reads the line:
MessageBox.Show(c.Item(1).Name) it is doing more work because it has to:
a) Find out what c.Item(1) is - a clsSprite
b) Look for a Name property

It's better to let the compiler know that c.Item(1) is a clsSprite in design time (in code) instead of in Runtime
(letting it find out on its own). To do that, change that line to:
 MessageBox.Show(DirectCast(c.Item(1), clsSprite).Name)

You're telling it to use c.Item(1), and you're telling the compiler it is a clsSprite and you wish to access the .Name property. Doing it this way is:
a) Faster
b) Harder to type - You're a programmer. Get over it.
c) Less prone to mistakes (When you type the dot, a box comes up giving a list of choices).

Alright. A collection is usually meant to store a massive amount of objects. So let's add a bunch of clsSprites (do this after you add the first one):
c.Add(New clsSprite("Guy in Collection2"))
c.Add(New clsSprite("Guy in Collection3"))

Now instead of going around messageboxing each name for each element manually, we might as well do it in a loop (Replace the other messagebox line):
For x = 1 To c.Count
   MessageBox.Show(DirectCast(c.Item(x), clsSprite).Name)
Next

Now run it. You should get 3 messageboxes.

One more thing about collections. Besides being able to add items, you can also remove items (quite easily) :
After the loop, add this:
c.Remove(1)
'This is a second loop
 For x = 1 To c.Count
MessageBox.Show(DirectCast(c.Item(x), clsSprite).Name)
Next


Now run it.  It should display:
Guy in Collection
Guy in Collection2
Guy in Collection3
'Next loop
Guy in Collection2
Guy in Collection3

Oh and that's not all. The collection smartly "bumped" everything down. Add these lines again:
c.Remove(1)
'This is a third loop
 For x = 1 To c.Count
MessageBox.Show(DirectCast(c.Item(x), clsSprite).Name)
Next

Now it should display:
Guy in Collection
Guy in Collection2
Guy in Collection3
'Next loop
Guy in Collection2
Guy in Collection3
'Next loop
Guy in Collection3

Smart huh?
Tutorial Note: The MessageBoxes will "minimize" automatically if your mouse is not on the messagebox and you hit enter (or space) to close the box. To prevent this, it's easier to manually click the "Ok" button with your cursor.

There's more! Collections have a property called "Keys" also. Delete all the loops except the first one. (And delete all the lines that say c.Remove(1) too).
Replace the c.Add lines with these:

c.Add(New clsSprite("Guy in Collection"), "GC1")
c.Add(New clsSprite("Guy in Collection2"), "GC2")
c.Add(New clsSprite("Guy in Collection3"), "GC3")


 If you wanted to remove a guy from the collection, but you've bumped it so much that you don't know what index he's on, you can remove him using the Key arguments I've added. (By the way GC stands for Guy in Collection)

After your first loop (You should only have one actually):
c.Remove("GC2")
'A second loop
For x = 1 To c.Count
MessageBox.Show(DirectCast(c.Item(x), clsSprite).Name)
Next

Run it, you should get:
Guy in Collection
Guy in Collection2
Guy in Collection3
'Next loop
Guy in Collection
Guy in Collection3

Now watch what happens if you add this:
c.Remove("GC2")
'A third loop
For x = 1 To c.Count
MessageBox.Show(DirectCast(c.Item(x), clsSprite).Name)
Next

You get:
Guy in Collection
Guy in Collection2
Guy in Collection3
'Next loop
Guy in Collection
Guy in Collection3
<ERROR!>

You've already removed GC2! You can't remove it again. To prevent problems like this in your game, I recommend wrapping a Collection.Remove line with Try...Catch..End Try block. So take your third loop and modify it like so:

Try
c.Remove("GC2")
Catch
'Do nothing. Just don't remove the object in the collection
End Try
'A third loop
For x = 1 To c.Count
MessageBox.Show(DirectCast(c.Item(x), clsSprite).Name)
Next


A Try..Catch..End Try block works like so: If the line(s) in the Try block have an error, it goes to the Catch and executes what's in there. Simple enough. You know what's going to happen.  It will display:
Guy in Collection
Guy in Collection2
Guy in Collection3
'Next loop
Guy in Collection
Guy in Collection3
'Third loop. Same as before because you don't remove anything.
Guy in Collection
Guy in Collection3

So as you can tell, collections are pretty great! However they do have a few... quirks. There is no way to retreive the Key value based on the index. For example you can't say:
MessageBox.Show(c.Key(1)) <-- This doesn't exist. It won't messagebox "GC1". So there's no way to retreive the key value by entering in the index.

Collections
-Pros:
  *Versatile
  *Simple to use
  *Indices start at 1 (I Guess this is easier for some people?)
  *Keys
-Cons
  *You need (or, it is highly recommended to have) a Try...Catch...End Try block  whenever you remove an element
  *Not many features besides Add and Remove
  *No way to retrieve key value based on index.

ArrayList
Now it's time to check out array lists. Since your form1_load code is getting quite messy, I'd recommend deleting all the code in there.

In your Globals:
Dim a As New Collections.ArrayList()

ArrayLists don't have keys unfortunately. Also note,  Collections (right above) is different from a Collection (what we were using above). In this case, Collections is a namespace (a "Grouping" so to speak) - We'll learn more about namespaces later. Just note the difference.

In the Load procedure:
a.Add(New clsSprite("Guy in array list"))
a.Add(New clsSprite("Guy in array list2"))
a.Add(New clsSprite("Guy in array list3"))

As the name implies, ArrayLists are based off of arrays. So it has a "Base" 0:
For x = 0 To (a.Count - 1) 'Array lists start with 0
    MessageBox.Show(DirectCast(a.Item(x), clsSprite).Name)
Next

When you type in a. ("a dot"), and look at the Intellisense box, you'll notice a LOT more procedures (subs/functions/events) than in a Collection. Arraylists are more versatile and have more features than a Collection.

To remove an element from an ArrayList, you use the RemoveAt function instead of the Remove function. Add the following code:
 a.RemoveAt(0)
For x = 0 To (a.Count - 1) 'Array lists start with 0
MessageBox.Show(DirectCast(a.Item(x), clsSprite).Name)
Next
a.RemoveAt(0)
For x = 0 To (a.Count - 1) 'Array lists start with 0
MessageBox.Show(DirectCast(a.Item(x), clsSprite).Name)
Next
a.RemoveAt(0)
For x = 0 To (a.Count - 1) 'Array lists start with 0
MessageBox.Show(DirectCast(a.Item(x), clsSprite).Name)
Next

You'll get:
Guy in array list
Guy in array list2
Guy in array list3

Guy in array list2
Guy in array list3

Guy in array list3

If you removeAt(0) again, you'll get an error. So as with Collections, be careful when you remove stuff.

Now let's use the Remove command. The Remove command, in an ArrayList, is used to manually remove objects. There is no key in an ArrayList. The Remove function takes an object (in our case a class, clsSprite) as its argument. You can't simply say a.Remove(New clsSprite("Guy in arraylist")) and expect it to get removed. The a.Add(New clsSprite("Guy in arraylist")) is actually different than the a.Remove(New clsSprite("Guy in arraylist")). I guess you could say, it's the exact same class but different instances. It's sort of like twins. They look the same but they're different.

So you'd have to do something like this (Delete all the code you have except Dim a As New ArrayList) :

Dim guy As New clsSprite("Guy in arraylist")
a.Add(guy)
a.Add(New clsSprite("Guy in array list2"))
a.Add(New clsSprite("Guy in array list3"))

For x = 0 To (a.Count - 1) 'Array lists start with 0
   MessageBox.Show(DirectCast(a.Item(x), clsSprite).Name)
Next
a.Remove(guy)
For x = 0 To (a.Count - 1) 'Array lists start with 0
   MessageBox.Show(DirectCast(a.Item(x), clsSprite).Name)
Next


You'll get:
Guy in array list
Guy in array list2
Guy in array list3

Guy in array list2
Guy in array list3

There. Simple enough. Now let's look at inserting elements in between other elements.
Guy in array list
Guy who wants to be in 2nd <-- we're going to squeeze this in between the other elements.
Guy in arraylist2
Guy in arraylist3

Delete your loops.  Replace them with:
For x = 0 To (a.Count - 1) 'Array lists start with 0
    MessageBox.Show(DirectCast(a.Item(x), clsSprite).Name)
Next

'Remember, an index of 1 is really 2nd.
a.Insert(1, New clsSprite("Guy who WANTS to be 2nd"))
For x = 0 To (a.Count - 1) 'Array lists start with 0
     MessageBox.Show(DirectCast(a.Item(x), clsSprite).Name)
Next


You'll get exactly what I said above. This would be so much more time-consuming using actual arrays.

Now let's reverse the elements. Remove your loops (and the a.Insert line), replace them with:
For x = 0 To (a.Count - 1) 'Array lists start with 0
    MessageBox.Show(DirectCast(a.Item(x), clsSprite).Name)
Next

a.Reverse()
For x = 0 To (a.Count - 1) 'Array lists start with 0
    MessageBox.Show(DirectCast(a.Item(x), clsSprite).Name)
Next


You'll get:
Guy in array list
Guy in array list2
Guy in array list3

Guy in array list3
Guy in array list2
Guy in array list

Now notice one thing. You can also have arguments for the function reverse. Let's say you wanted to reverse the 2nd and 3rd elements (thus, element 1 and element 2 since arrays are 0 based).

Replace the a.Reverse line with:
 a.Reverse(1, 2)

Run it, 3 and 2 will be reversed.

Note: this does NOT mean "Reverse elements 1 and 2." It means, starting from element 1 (the 2nd element), reverse 2 elements. So to reverse the entire thing, you'd do a.reverse(0, 3) <-- "Starting from the first element, replace 3 elements". Or you could simply write it as a.Reverse()


Heck,  you can even sort this alphabetically (only if you add strings to the ArrayList instead of classes) using a.Sort(). You can also store the classes into an array of classes (for whatever reason) by using a.ToArray.

One more thing before we finish ArrayLists. You can specify an "initial capacity".
Dim a as New ArrayList(2)

Unlike Arrays, if your ArrayList has more elements than what you specified, you won't get an error. The 'initial capacity' gives the program an approximate number of elements that you're going to be storing so that it can allocate memory for it. So by specifying a capacity you're doing yourself a favor by optimizing (just a little though) <-- this is where ArrayLists gets a slight speed advantage over Collections.

ArrayLists

-Pros:
  *Versatile
  *Faster than Collections if
initial capacity specified
  *Multitude of features, especially for sorting strings.
-Cons
  *You need a Try...Catch...End Try whenever you remove an element
  *No keys! (This might make some get mad - including me)
  *ArrayLists are 0 based - I don't mind this at all. But apparently others do.

Hashtables
Again, delete all the code you have in form1_load to keep it clean. Hashtables are, I guess you could say, an "upgrade" to ArrayLisys. They work a little differently. You can specify an initial capacity. Hashtables are 1 based.

 Dim h As New Collections.Hashtable(3)

They work just like Collections or ArrayLists, however, the key value and the object is reversed (I honestly don't know why Microsoft doesn't keep a standard for these things....) :
 h.Add("GIH", New clsSprite("Guy In Hashtable1"))
h.Add("GIH2", New clsSprite("Guy In Hashtable2"))
See how the key value and the sprite are reverse?

If you type in h. (h dot) and look through the intellisense box, most of its properties are the same as ArrayLists. However, note one thing, when you look at the tooltip for h.Add, you'll notice that the Key doesn't have to be a string. It can be any object (any class, any integer, anything!) You could even have another clsSprite as a key!
Add this line:
Dim key3 as new clsSprite("Key in hashtable3")
 h.Add(key3, New clsSprite("Guy in Hashtable3"))

Weird huh?

Also note, when you want to access a Hashtable's items, you can't use the index. Type in h.item(  - leaving the parentheses open. The tool tip will ask you for a key not an index! Really weird.

So to access an item in a Hashtable:
 MessageBox.Show(DirectCast(h.Item("GIH"), clsSprite).Name)

Or to access the one with the clsSprite as the key:
 MessageBox.Show(DirectCast(h.Item(key3), clsSprite).Name)

You'll get:
"Guy in Hashtable1"
"Guy in Hashtable3"

See the advantage of using Hashtables is when you've got a defined set of objects (that you've dimmed) and you want to add/remove them at will. Also, you can use any type of object as a key (even Integers). As you'll see below, you can also get the key values. Remove the code you have in form1_load and replcae it with:
h.Add("c", New clsSprite("Guy In Hashtable1"))
h.Add("a", New clsSprite("Guy in hashtable2"))
h.Add("b", New clsSprite("Guy In Hashtable3"))


Dim Entry as Collections.DictionaryEntry

This is a neat little loop that you haven't seen before (in my tutorials at least):
For Each Entry In h
    MessageBox.Show(Entry.Key)
Next

You'll get:
a
b
c

I'll explain what just happened. Hashtables automatically sort their items in alpabetical order. When you say For Each Entry In h, you're asking it for each DictionaryEntry in the hashtable. A DictionaryEntry is a bunch of data from the hashtable (which include Values and Keys).  This is pretty much the way to retreive keys.

Delete the h.add lines and replace them with:
h.Add(New clsSprite("GIH1"), New clsSprite("Guy In Hashtable1"))
h.Add(New clsSprite("GIH2"), New clsSprite("Guy in hashtable2"))
h.Add(New clsSprite("GIH3"), New clsSprite("Guy In Hashtable3"))

Now replace the MessageBox line with:
MessageBox.Show(DirectCast(h.Item(Entry.Key), clsSprite).Name)

The MessageBox line might confuse you a bit. Remember that to access a Value, you have to enter the Key, that's why we're using Entry.Key. And from there on it's stuff you've seen before (the DirectCast... the h.Item...etc).

For some really strange reason, you should get this:
"Guy in hashtable3"
"Guy in hashtable2"
"Guy in hashtable1"

It can't sort clsSprites alphabetically (nor does it know to sort their .Name properties), so it basically writes them backwards I guess. (Hashtables are written like a Stack, you place objects at the top of the stack, but you read starting from the bottom).

That's pretty much it for Hashtables.
Note: For VS.NET 2003 users, instead of dimming Entry as DictionaryEntry, you can do this in the loop itself:
For Each Entry As DictionaryEntry In h. Neat feature (Unfortunately this doesn't work in VS.NET 2002

Hashtables
-Pros:
  *Versatile
  *Faster than Collections if
initial capacity specified
  *Multitude of features.
  *The Key property can be anything. Allows for object-object (aka class-class) pairings instead of being limited to string-object (aka string-class) pairings.
  *The initial loading time is much quicker than that of ArrayLists and Collections. I found this through trial and error with my 3D RPG. 
-Cons
  *Requires the use of a Dictionary Entry to access an element
  *Doesn't store the elements in order, tries to sort them alphabetically.
  *Neither 0 nor 1 based. In fact, it's just a listing of elements.

Stacks
Delete all the code in form1_load.

Stacks (and Queue's which we'll talk about soon) fall into a completely different category. They aren't used for storing data, however. They're mainly used for data management (sounds boring huh? Stay alive. You'll get through it).

 Dim s As New Collections.Stack()

Now to add an element you have to use Push instead of Add. To an item in a stack
s.Push(New clsSprite("Guy in stack1"))
s.Push(New clsSprite("Guy in stack2"))
s.Push(New clsSprite("Guy in stack3"))
s.Push(New clsSprite("Guy in stack4"))

 Think about a stack of pancakes. The pancakes you put in first are on the bottom, the one you put in last is on the top. You can't take out the bottom pancake without removing the ones on top. The one on top is clearly visible to you so you can take a peek at it.

MessageBox.Show(DirectCast(s.Peek, clsSprite).Name)

It should say "Guy in stack4." Now delete that line.

In order to access the bottom pancake, you remove the ones on the top. You might as well messagebox those while you're at it. To remove the pancakes on top, you push them out.

For x = 1 To s.Count
   MessageBox.Show(DirectCast(s.Pop, clsSprite).Name)
Next

You should get
Guy in stack4
Guy in stack3
Guy in stack2
Guy in stack1

Now note one thing. Once you've popped an element out of the stack, it's gone. So just to retrieve the bottom element
, you had to discard the rest. This is what I mean by Stacks are meant for data management instead of data storage.

Take a networking game for example (I know I don't have any networking tutorials (as of 1/11/2005) yet). Here's a basic concept. Let's say you're making a tile game and you want to make it optimized, so you make it so that the most-used tiles are sent to the player first, then followed by the least-used tiles. You would first Push (Add) the least-used tiles in the stack, followed by the most-used tiles, this is all server-side. You would then pop the most-used tiles to return to the players in the game, and end up emptying the list. You would repeat the process each frame I guess (to have it constantly updated). I know this example might mean much to you (since I haven't done Networking yet) but trust me, stacks are important.

Stacks are described by Microsoft as Last-In First-Out (The last element you put in comes out first, which is why "Guy in stack4" comes out first)

Queue's
Besides  having too many vowels, Queue's are another useful feature in VB.NET. They're essentially backwards stacks. Micrsooft describes them as "First in, First out."

Delete the code in form1_load and add the following

Dim q As New Collections.Queue()

'Enqueue is the same as Push (Add) in a Stack
q.Enqueue(New clsSprite("Guy in Queque1"))
q.Enqueue(New clsSprite("Guy in Queque2"))
q.Enqueue(New clsSprite("Guy in Queque3"))
q.Enqueue(New clsSprite("Guy in Queque4"))


MessageBox.Show(DirectCast(q.Peek, clsSprite).Name)

Would give you "Guy in queue1." Think about a line in front of a movie theater. The line (queue) starts in the front, and ends in the back. When you peek, you look at the guy in the front of the line.

Now replace the messagebox statement with:

'Dequeue is like Pop in a Stack.
For x = 1 To q.Count
   MessageBox.Show(DirectCast(q.Dequeue, clsSprite).Name)
Next

You should get:
Guy in Queue1
Guy in Queue2
Guy in Queue3
Guy in Queue4

Going back to the movie theater scenario. When you Dequeue someone, you kick them out from the front of the line.

Stacks and Queues
-Pros:
  *Data Management
  *The Key property can be anything. Allows for object-object (aka class-class) pairings instead of being limited to string-object (aka string-class) pairings.
-Cons
  *NOT good for storage
  *No Key

Summary
Everything has its own purpose.
ArrayLists are typically used to store different types of classes and if the number of elements changes often.
Collections/Hashtables are usually used to store one type of class (but not always)
Queues/Stacks are used for data management

Of ArrayLists, Collections, and Hashtables, I would actually choose Hashtables (when dealing with games). Why?
ArrayLists don't have keys, a feature that is very commonly used.
Collections are simple, not too many features that you might need.
Hashtables have a lot of features (most of which you don't need in a game... but that's a bonus), and most importantly they're faster than ArrayLists or Collections. I found this through trial and error when making my 3d RPG. It's annoying how it sorts everything in alpabetical order (use this only if the order is of importance to you)

That's pretty much it. If you don't understand, be sure you're following along with .NET since this tutorial is a trial-and-error style tutorial. Re-do this if you don't understand making sure you actually type out the code; this way, you'll get to see what some of the arguments are and how Microsoft tries to explain them.

For an application of this tutorial, be sure to check out the 4th tutorial in the RPG Programming Series.  

There is no source code for this tutorial (it doesn't accomplish anything)