We've learned a lot about arrays and how to manage the content of an array, but there other types of collections available in .NET for you to use with C#.In this module, we will learn about a few of those types and show how they can each represent an element of a card game.
Download this lesson as a Polyglot Notebook to open in Visual Studio Code, or open directly in your web browser with Binder.
Hashtable and SortedList
A Hashtable and SortedList are collections of key/value pairs that contain no duplicate keys. The Hashtable
is sorted based on the hash of the keys and a SortedList
is sorted based on the key value.
Let's put three cards into a SortedList and a Hashtable and see how they're sorted differently.We'll use a hand with a poker straight flush of 2, 3, 4, 5, and 6 of clubs:
//var hand = new Hashtable(); var hand = new SortedList(); // These statements add a value to the collection in the form (key, value) hand.Add("2C", "2C"); hand.Add("3C", "3C"); hand.Add("4C", "4C"); hand.Add("5C", "5C"); hand.Add("6C", "6C"); display(hand)
Try commenting and uncommenting the definition of the deck and observe how it sorts the collection differently for each of the types. The Hashtable is not stored in the order by the raw values of the key, but by the hashed values of the key. Those hashed keys mean that it is quicker to look up those values from the Hashtable than the SortedList. Let's do a simple performance test, fetching a card from our hand of 5 cards 10 million times. This data set is so small, we need to fetch a random card millions of times in order to observe a difference in performance.
Try running this block again, and we'll choose a card from the hand based on a random rank value. Run the code for both the SortedList and the Hashtable and observe the time that it takes to select a card from the hand 10 million times.
var hand = new SortedList(); //var hand = new Hashtable(); // These statements add a value to the collection in the form (key, value) hand.Add("2C", "2C"); hand.Add("3C", "3C"); hand.Add("4C", "4C"); hand.Add("5C", "5C"); hand.Add("6C", "6C"); var random = new Random(); for (var i=0; i<10_000_000; i++) { ? var rank = random.Next(2, 7); ? var foundCard = hand[$"{rank}C"]; }
Which one would you use to model a hand of cards?
Another key feature of the SortedList and Hashtable is that they prevent duplication of keys in their collection. Let's look at our hand of cards and try to add a card with the same key:
var hand = new SortedList(); //var hand = new Hashtable(); // These statements add a value to the collection in the form (key, value) hand.Add("2C", "2C"); hand.Add("3C", "3C"); hand.Add("4C", "4C"); hand.Add("5C", "5C"); hand.Add("6C", "6C"); // Add a duplicate hand.Add("2C", "7C");
Even though the value of "7C" is unique in the collection, both SortedList and Hashtable throw an error when adding a value with a key that already exists.
We've been adding cards to the collection, but we can also remove cards from the collection with the Remove
method:
//var hand = new SortedList(); var hand = new Hashtable(); // These statements add a value to the collection in the form (key, value) hand.Add("2C", "2C"); hand.Add("3C", "3C"); hand.Add("4C", "4C"); hand.Add("5C", "5C"); hand.Add("6C", "6C"); // Remove a card hand.Remove("2C"); display(hand);
Queues
A Queue is a collection of objects stored and accessed in a first-in / first-out manner. Enqueue
to add elements to the Queue
and Dequeue
to remove elements from the Queue
. You can also Peek
to inspect the oldest element in the Queue
In the scenario of building a card game, a Queue might represent the collection of cards that is being delivered to a player by the dealer. This is not their hand, but the middle collection that represents that pile of cards the dealer builds in front of the player. The first card delivered to the player is picked up first, then the second, and so forth
var myHand = new Queue(); myHand.Enqueue("2C"); myHand.Enqueue("3C"); myHand.Enqueue("4C"); myHand
Our hand contains the three cards, and they reside in the order that they arrived in our hand. We can look at several properties of the queue so that we can measure and interact with the queue properly.
We can count the number of entries in the queue:
myHand.Count
We can also peek at the first entry in the queue without removing it from the queue:
display(myHand.Peek()); display(myHand);
We can finally dequeue an entry from the queue and this will remove the first entry and return that value:
display(myHand.Dequeue()); myHand
Stacks
We've modeled a hand of cards and the process of dealing a hand of cards, but what about the deck of cards? We've previously modeled this as an array and while that worked its not entirely accurate behavior of the deck of cards. We couldn't really remove a card or add a card to the deck.
A Stack is a collection that is accessed in Last-in/First-out manner using the Push
and Pop
methods to add and remove items, with the Peek
method available to examine the next item to be removed from the Stack
.The Stack
is our best representation of a deck of cards:the last card that is placed on the top of the deck is the first to be dealt to a player.
var myDeck = new Stack(); myDeck.Push("AD"); myDeck.Push("AS"); myDeck.Push("9H"); myDeck.Push("9S"); myDeck.Push("9C"); myDeck
Let's take a look at the top card of the deck using the Peek
method. Yea, you really are peeking at the first card on top of the deck!
var myCard = myDeck.Peek(); myCard // The 9-Clubs is returned first because it was Pushed onto the Stack LAST
We can peek at the top card and then we can Pop
that card of the top of the stack:
display(myDeck.Peek());
var thisCard = myDeck.Pop();
display(thisCard);
display(myDeck);