|
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) |