{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Session 5: Collections, Generics, and a peek into LINQ\n", "\n", "We've worked with loops, conditions, methods, and our own types in C# but what about collections of objects? A group of Person objects or Products that are added to a shopping cart, how do we handle those? \n", "\n", "## Collections\n", "\n", "There are a number of different collection objects that you can use that implement the same basic interactions.\n", "\n", "### Array\n", "\n", "[Arrays](https://docs.microsoft.com/dotnet/csharp/programming-guide/arrays?WT.mc_id=visualstudio-twitch-jefritz) are reference types and the simplest of the collection types, and can be declared with one to many dimensions and can also be declared jagged. Simplify declared with a type and square brackets `[ ]` defining the size of the array, initialized with a `new` statement and curly braces `{ }` optionally containing the initial values of the array." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ "\r\n", "
\r\n", " \r\n", " \r\n", "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "Array is created: True" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "Array is null: False" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "Array Size: 3" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "Array Size: 3" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "int[] numbers;\n", "\n", "// Numbers doesn't contain anything, as it wasn't assigned yet\n", "display(\"Array is created: \" + (numbers == null).ToString());\n", "\n", "// Create an array by using square brackets containing a size \n", "numbers = new int[3];\n", "display(\"Array is null: \" + (numbers == null).ToString());\n", "\n", "// The read-only Length property shows the number of elements in the array \n", "display(\"Array Size: \" + numbers.Length);\n", "\n", "// Declare the array with initial values\n", "var fullArrayOfNumbers = new int[3] {1, 2, 3};\n", "display(\"Array Size: \" + fullArrayOfNumbers.Length);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can then interact with the values of the array using numeric a numeric indexer starting with a base value of 0" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Item[0]: 1" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "Item[0]: 5" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "display(\"Item[0]: \" + fullArrayOfNumbers[0]);\n", "\n", "// You can set values on the array using the equals assignment operator\n", "fullArrayOfNumbers[0] = 5;\n", "display(\"Item[0]: \" + fullArrayOfNumbers[0]);" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "ename": "Unhandled exception", "evalue": "System.IndexOutOfRangeException: Index was outside the bounds of the array.\r\n at Submission#6.<>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)", "output_type": "error", "traceback": [ "System.IndexOutOfRangeException: Index was outside the bounds of the array.\r\n at Submission#6.<>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)", " at Submission#6.<>d__0.MoveNext()", "--- End of stack trace from previous location where exception was thrown ---", " at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)" ] } ], "source": [ "// You cannot interact with array values outside the size of the array\n", "// display(fullArrayOfNumbers[5]);\n", "fullArrayOfNumbers[5] = 100;" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "6" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "2" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// You can work with multi-dimensional arrays as well\n", "var matrix = new int[3,2] { {1,2}, {3,4}, {5,6} };\n", "display(matrix.Length);\n", "\n", "// Access elements of the multi-dimensional using a comma between index values\n", "matrix[0,1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The challenge with arrays is that you cannot easily add or remove objects from the array without going through a complex bit of resizing using the `Array.Resize` method." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexvalue
01
12
23
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "var myNumbers = new int[] {1,2,3};\n", "display(myNumbers);\n", "\n", "// This doesn't work\n", "//myNumbers.Add(4);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can change the size of a one-dimensional array using the [Array.Resize](https://docs.microsoft.com/dotnet/api/system.array.resize?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz) method. This method does not _just_ resize the array, but rather creates a new array of the desired size and copies the values into that new array. " ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexvalue
01
12
23
34
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// This does\n", "Array.Resize(ref myNumbers, 4);\n", "myNumbers[3] = 4;\n", "display(myNumbers);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "Warning: If you have a reference to the array somewhere else in your code, that reference will NOT be resized as well. Arrays are reference types, and references to the prior object are persisted.\n", "
" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexvalue
01
12
23
34
4100
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// Capture a reference to myNumbers\n", "var referenceToMyNumbers = myNumbers;\n", "\n", "// Change myNumbers\n", "Array.Resize(ref myNumbers, 5);\n", "myNumbers[4] = 100;\n", "display(myNumbers);" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexvalue
01
12
23
34
" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Let's see what referenceToMyNumbers contains:\n", "referenceToMyNumbers" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`referenceToMyNumbers` contains the contents of `myNumbers` before the resize because it was NOT effected by the `Array.Resize` operation which makes a copy of the array.\n", "\n", "You can remove elements from the array by resizing to a smaller number of elements. This will eliminate contents from the end of the array." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexvalue
01
12
23
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// Remove is similar, and eliminates elements from the end of the array\n", "Array.Resize(ref myNumbers, 3);\n", "display(myNumbers);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Arrays are enumerable and implement the `IEnumerable` interface, meaning you can iterate over the contents of a collection with a loop and interact with them:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "1" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "2" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "3" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// i in this case returns the element in the collection, not the index\n", "\n", "foreach (var i in myNumbers) {\n", " display(i);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Array.Fill" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexvalue
01
11
21
" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var myOneArray = new int[3];\n", "Array.Fill(myOneArray, 1);\n", "\n", "myOneArray" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Hashtable and SortedList\n", "\n", "A [Hashtable](https://docs.microsoft.com/dotnet/api/system.collections.hashtable?view=netcore-3.1?WT.mc_id=visualstudio-twitch-jefritz) and [SortedList](https://docs.microsoft.com/en-us/dotnet/system.collections.sortedlist?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz) are collections of key/value pairs that contain no duplicate keys. The `Hashtable` is sorted based on the hash hash of the keys and a `SortedList` is sorted based on the key value" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
keyvalue
jpgJpeg Compressed Images
mp3Compressed Music
txtPlain text
" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "//var fileExt = new Hashtable();\n", "var fileExt = new SortedList();\n", "fileExt.Add(\"txt\", \"Plain text\");\n", "fileExt.Add(\"mp3\", \"Compressed Music\");\n", "fileExt.Add(\"jpg\", \"Jpeg Compressed Images\");\n", "\n", "fileExt" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "// No duplicates are allowed\n", "//fileExt.Add(\"mp3\", \"Sound effects\");" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Compressed Music" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "fileExt[\"mp3\"]" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "jpg" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "mp3" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "txt" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "foreach (var kv in fileExt) \n", "{\n", " display(((DictionaryEntry)kv).Key);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Queue\n", "\n", "A [Queue](https://docs.microsoft.com/dotnet/api/system.collections.queue?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz) 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`" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexvalue
0First
1Second
2Third
" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var myQueue = new Queue();\n", "myQueue.Enqueue(\"First\");\n", "myQueue.Enqueue(\"Second\");\n", "\n", "myQueue.Enqueue(\"Third\");\n", "\n", "myQueue" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "3" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Use Count to check the size of the queue\n", "myQueue.Count" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "First" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// Use Peek to inspect the next value off of the queue\n", "display(myQueue.Peek());" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "First" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var z = myQueue.Dequeue();\n", "z" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Stack\n", "\n", "A [Stack](https://docs.microsoft.com/dotnet/api/system.collections.stack?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz) 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`. I think of a `Stack` like 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." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexvalue
09-c
19-s
29-h
3A-s
4A-d
" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var myHand = new Stack();\n", "myHand.Push(\"A-d\");\n", "myHand.Push(\"A-s\");\n", "myHand.Push(\"9-h\");\n", "myHand.Push(\"9-s\");\n", "myHand.Push(\"9-c\");\n", "\n", "myHand" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "9-c" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var myCard = myHand.Peek();\n", "myCard // The 9-Clubs is returned first because it was Pushed onto the Stack LAST" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "9-c" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "9-s" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "9-h" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "A-s" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "A-d" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "9-c" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "9-c" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "foreach (var item in myHand) \n", "{\n", " display(item);\n", "}\n", "\n", "display(myHand.Peek());\n", "var thisCard = myHand.Pop();\n", "thisCard" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We've looked at these basic interactions with collection types and the keys or values stored don't have a specific type associated. You can get into some hairy situations dealing with type conversions if you mix and match types for keys or values." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indextypeRankSuitvalue
0Submission#26+CardJh
1System.StringJoker
2Submission#26+CardKh
3Submission#26+CardAd
" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Card {\n", " public string Rank;\n", " public string Suit;\n", " public Card(string id) {\n", " Rank = id.Split('-')[0];\n", " Suit = id.Split('-')[1];\n", " }\n", " public override string ToString() { return Rank + \"-\" + Suit; }\n", "}\n", "\n", "var deckOfCards = new Stack();\n", "deckOfCards.Push(new Card(\"A-d\"));\n", "deckOfCards.Push(new Card(\"K-h\"));\n", "\n", "// Now add a Joker card\n", "deckOfCards.Push(\"Joker\");\n", "\n", "deckOfCards.Push(new Card(\"J-h\"));\n", "\n", "deckOfCards" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
RankSuit
Jh
" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// take a card off the deck\n", "var myCard = deckOfCards.Pop();\n", "myCard" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Generics \n", "\n", "[Generics](https://docs.microsoft.com/dotnet/csharp/programming-guide/generics?WT.mc_id=visualstudio-twitch-jefritz) are a way for you to force the type of a parameter from within client code. You declare the type generically using the convention `` with a class name or a method name and this allows that type to be passed around and enforced on those methods or properties in a class.\n", "\n", "Most developers are familiar with using [Generic Collections](https://docs.microsoft.com/dotnet/standard/generics/collections?WT.mc_id=visualstudio-twitch-jefritz), which enforce the type of the objects in the collection. You'll find a `Queue` and a `Stack` available in the `System.Collections.Generic` namespace that mirror the versions we used above, as well as a few others list `List` and `Dictionary`.\n", "\n", "Let's take a look at some examples\n", "\n", "### List<T>\n", "\n", "The [List<T>](https://docs.microsoft.com/dotnet/api/system.collections.generic.list-1?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz) is the most flexible of the generic collections, allowing you to add, remove, and access objects of the specified type. Let's take a look at that deck of cards sample again:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/html": [ "System.Collections.Generic.List<Submission#26+Card>" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
indexRankSuit
0Ad
1Jd
29c
38s
" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Declare the list with the specified type inside angle-brackets\n", "var listOfCards = new List();\n", "listOfCards.Add(new Card(\"A-d\"));\n", "listOfCards.Add(new Card(\"J-d\"));\n", "listOfCards.Add(new Card(\"9-c\"));\n", "listOfCards.Add(new Card(\"8-s\"));\n", "\n", "display(listOfCards.GetType());\n", "listOfCards" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can randomly access an element anywhere in the `List`, similar to choosing a card from the middle of the deck:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
RankSuit
9c
" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "listOfCards[2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similary, we can also `Insert` to add a card into the middle of the deck at a specific index. Perhaps I have the three of hearts and want to insert it as the third card in the collection:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexRankSuit
0Ad
1Jd
23h
39c
48s
" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var ThreeHearts = new Card(\"3-h\");\n", "listOfCards.Insert(2, ThreeHearts);\n", "listOfCards" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I can ask the list where the three of hearts is located by using the `IndexOf` method to locate it in the deck. I bet that sleight of hand magician could use this technique to find the three of hearts in a deck of cards. Its not magic, just C#:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/html": [ "2" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "listOfCards.IndexOf(ThreeHearts)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also ask the `List` what the card is at a specific index by using the `ElementAt` method" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
RankSuit
Jd
" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "listOfCards.ElementAt(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This list is generically typed to a `Card` and we read the type `List` in English as \"List **of type** Card\". Thanks to this typing, we cannot add anything that isn't a card to the `listOfCards`:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [], "source": [ "//listOfCards.Add(\"Joker\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Dictionary<TKey,TValue>\n", "\n", "In some classes, you may have multiple type-arguments like the Dictionary class. In [Dictionary<TKey,TValue>](https://docs.microsoft.com/dotnet/api/system.collections.generic.dictionary-2?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz) there are type arguments for the key and the value stored." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
keyvalue
txtPlain text
mp3Compressed Music
jpgJpeg Compressed Images
" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var exts = new Dictionary();\n", "exts.Add(\"txt\", \"Plain text\");\n", "exts.Add(\"mp3\", \"Compressed Music\");\n", "exts.Add(\"jpg\", \"Jpeg Compressed Images\");\n", "\n", "exts" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Jpeg Compressed Images" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "exts[\"jpg\"]" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": [ "//exts.Add(\"mp3\", \"Sound Effects\");" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "//exts.Add(\"card\", new Card(\"J-h\"));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Hashset\n", "\n", "A [Hashset](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz) is a high-performance collection that does not contain duplicate entries." ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexRankSuit
0Jc
1Ac
29d
33h
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "var set = new HashSet();\n", "set.Add(new Card(\"J-c\"));\n", "set.Add(new Card(\"A-c\"));\n", "set.Add(new Card(\"9-d\"));\n", "\n", "var threeHearts = new Card(\"3-h\");\n", "set.Add(threeHearts);\n", "\n", "display(set);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we attempt to add the 3 of Hearts a second time, it doesn't actually add another card to the Hashset because the 3 of Hearts is already present:" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexRankSuit
0Jc
1Ac
29d
33h
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "set.Add(threeHearts);\n", "display(set);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Creating Generic Classes\n", "\n", "Ok, generics are cool... but how do you create your own classes or methods to work with them? Perhaps we have our own custom collection object that randomly inserts objects into the collection and we want to work with the objects generically.\n", "\n", "The official documentation on [Generics](https://docs.microsoft.com/dotnet/standard/generics/?WT.mc_id=visualstudio-twitch-jefritz) has more details about how to interact with the managed types of the class.\n", "\n", "Get started by declaring your class and methods using the `` notation to indicate that this is a generic type-parameter where the characters inside the angle-brackets are identical for all methods with the same characters." ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
_Inner
[ 2, 4, 1, 3, 5 ]
" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class FritzSet \n", "{\n", " // This normally wouldn't be public scoped, but making it public for the notebook visualizer\n", " // The for this List is the same type as the that this class will be created with\n", " public List _Inner = new List();\n", " \n", " // The newItem parameter will be of type T, matching the same type assigned when this class is created\n", " public void Add(T newItem) \n", " {\n", " var insertAt = _Inner.Count == 0 ? 0 : new Random().Next(0,_Inner.Count+1);\n", " _Inner.Insert(insertAt, newItem);\n", " }\n", " \n", "}\n", "\n", "// Let's insert some numbers into a FritzSet of type 'int'\n", "var set = new FritzSet();\n", "set.Add(1);\n", "set.Add(2);\n", "set.Add(3);\n", "set.Add(4);\n", "set.Add(5);\n", "set" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
_Inner
[ { Submission#26+Card: Rank: 9, Suit: d }, { Submission#26+Card: Rank: A, Suit: d }, { Submission#26+Card: Rank: J, Suit: h }, { Submission#26+Card: Rank: 2, Suit: s }, { Submission#26+Card: Rank: 3, Suit: c } ]
" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// We can also create a FritzSet that holds a bunch of Card objects\n", "var deck = new FritzSet();\n", "deck.Add(new Card(\"A-d\"));\n", "deck.Add(new Card(\"9-d\"));\n", "deck.Add(new Card(\"J-h\"));\n", "deck.Add(new Card(\"3-c\"));\n", "deck.Add(new Card(\"2-s\"));\n", "\n", "deck" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
_Inner
[ { Submission#26+Card: Rank: J, Suit: h }, Joker ]
" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// you can also force a generic type of 'object' and lose that compile-time type checking\n", "var objSet = new FritzSet();\n", "objSet.Add(new Card(\"J-h\"));\n", "objSet.Add(\"Joker\");\n", "\n", "objSet" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What type of object are each element in the object set?" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/html": [ "Submission#26+Card" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "System.String" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "display(objSet._Inner[0].GetType());\n", "display(objSet._Inner[1].GetType());" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/html": [ "Submission#26+Card" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "System.String" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "// If we do a foreach with a var keyword, what type is emitted?\n", "foreach (var card in objSet._Inner) {\n", " display(card.GetType());\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introducing LINQ and Lambda Expressions with LINQ to Objects\n", "\n", "LINQ ([Language Integrated Query](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/linq?WT.mc_id=visualstudio-twitch-jefritz) which Fritz keeps calling 'Language Integrated Natural Query') refers to a collection of technologies that allow you to query data. We're going to start with a subset of LINQ called [LINQ to Objects](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/linq/linq-to-objects?WT.mc_id=visualstudio-twitch-jefritz) that allow you to add predicate methods to `IEnumerable` or `IQueryable` collections to query them. We will dig in further to discuss how LINQ and LINQ to Objects works in our next session, and in the rest of this notebook we'll explore some simple interactions that are available.\n", "\n", "Let's look at that `FritzSet` collection again and load it with some `Card`s" ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [], "source": [ "var deck = new FritzSet();\n", "deck.Add(new Card(\"A-d\"));\n", "deck.Add(new Card(\"A-h\"));\n", "deck.Add(new Card(\"A-c\"));\n", "deck.Add(new Card(\"A-s\"));\n", "deck.Add(new Card(\"9-d\"));\n", "deck.Add(new Card(\"J-h\"));\n", "deck.Add(new Card(\"3-c\"));\n", "deck.Add(new Card(\"2-c\"));\n", "deck.Add(new Card(\"7-d\"));\n", "deck.Add(new Card(\"6-d\"));\n", "deck.Add(new Card(\"5-d\"));\n", "deck.Add(new Card(\"4-d\"));\n", "deck.Add(new Card(\"4-h\"));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are a set of [Standard Query Operators](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/linq/standard-query-operators-overview?WT.mc_id=visualstudio-twitch-jefritz) that we can use to analyze our collection. Let's experiment with some of those operators for the remainder of this notebook.\n", "\n", "We can count the number of objects in the `deck` collection using the [Count](https://docs.microsoft.com/dotnet/api/system.linq.enumerable.count?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz) method with an optional expression called a [Lambda Expression](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/linq/query-syntax-and-method-syntax-in-linq?WT.mc_id=visualstudio-twitch-jefritz#lambda-expressions) to test each object to determine whether to count that entry." ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/html": [ "13" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "6" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Display the total number of cards in the list\n", "display(deck._Inner.Count());\n", "\n", "// Display the number of cards that are Diamonds suit.\n", "// Use the expression body member that tests for the Suit property to equal \"d\". Values that are TRUE are counted\n", "deck._Inner.Count(c => c.Suit == \"d\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also filter the collection by using the [Where](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.where?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz) method with a similar expression body member to test each member of the collection." ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexRankSuit
0Ad
16d
29d
34d
45d
57d
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "Count of Diamonds cards: 6" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "display(deck._Inner.Where(c => c.Suit == \"d\"));\n", "\n", "// Count the returned collection after it is filtered\n", "display(\"Count of Diamonds cards: \" + deck._Inner.Where(c => c.Suit == \"d\").Count());" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Just like the `Count` method above, you can chain together these methods to filter and return data." ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/html": [ "1" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Filter to JUST the Diamonds Then count the cards with rank Ace\n", "deck._Inner.Where(c => c.Suit == \"d\").Count(c => c.Rank == \"A\") " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also [Select](https://docs.microsoft.com/dotnet/api/system.linq.enumerable.select?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz) to return a different shaped collection of values from the collection. In other frameworks and languages this is sometimes referred to as `Map`" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexvalue
0A
16
29
34
45
57
" ] }, "execution_count": 45, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// This returns a collection of Rank values\n", "deck._Inner.Where(c => c.Suit == \"d\").Select(c => c.Rank) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also quantify the contents of our collection using [quantifier methods](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/linq/quantifier-operations?WT.mc_id=visualstudio-twitch-jefritz) like `Any` to query if any element matches a test condition and `All` to query if all elements match a test condition:" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/html": [ "False" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "/// This reminds me of the card game 'Go-Fish': Do you have any Queens? No? Go-Fish\n", "deck._Inner.Any(c => c.Rank == \"Q\") " ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [ { "data": { "text/html": [ "False" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Do you have a flush, all cards are the same suit?\n", "deck._Inner.All(c => c.Suit == \"d\") " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can chain together these operations to analyze and inspect the collection. \n", "\n", "Let's take that last example and filter out the cards in the clubs and hearts suits and see if we have a flush in diamonds:" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "data": { "text/html": [ "False" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "deck._Inner.Where(c => c.Suit != \"c\" && c.Suit != \"h\").All(c => c.Suit == \"d\") // Do you have a flush of Diamonds?" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "data": { "text/html": [ "True" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Do you have ANY cards that aren't clubs and are not hearts?\n", "deck._Inner.Where(c => c.Suit != \"c\" && c.Suit != \"h\").Any() " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can navigate around the collection using [First](https://docs.microsoft.com/dotnet/api/system.linq.enumerable.first?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz), [Skip](https://docs.microsoft.com/dotnet/api/system.linq.enumerable.skip?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz), and [Take](https://docs.microsoft.com/dotnet/api/system.linq.enumerable.take?view=netcore-3.1&WT.mc_id=visualstudio-twitch-jefritz) methods. \n", "\n", "- **First** will grab the first element from the collection, or with an optional lambda argument, can return the first element that passes that test.\n", "- **Skip** will pass over the first `n` number of elements\n", "- **Take** will return a collection from the first position of the collection of the size specified" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
RankSuit
Ad
" ] }, "execution_count": 61, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Get the first item in the collection\n", "deck._Inner.First()" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexRankSuit
0
J
h
1
6
d
2
4
h
3
7
d
4
A
c
5
A
d
6
5
d
7
A
s
8
A
h
9
9
d
10
2
c
11
3
c
" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Skip the first item in the collection and return the rest of the collection\n", "deck._Inner.Skip(1)" ] }, { "cell_type": "code", "execution_count": 64, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexRankSuit
03c
12c
" ] }, "execution_count": 64, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Just like at a poker game, skip 1 card and take the next 2 cards\n", "deck._Inner.Skip(1).Take(2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Interacting with a set of data is no fun without any kind of [sorting](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/linq/sorting-data?WT.mc_id=visualstudio-twitch-jefritz). We'll end with looking at the OrderBy, OrderByDescending, ThenBy, and ThenByDescending methods.\n", "\n", "- **OrderBy** orders the collection by a value provided in the method\n", "- **OrderByDescending** orders the collection in descending order by the value provided \n", "- **ThenBy** adds a secondary ordering to the collection after an OrderBy was specified\n", "- **ThenByDescending** adds a secondary ordering in descending order to the collection after an OrderBy was specified" ] }, { "cell_type": "code", "execution_count": 65, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexRankSuit
0
2
c
1
3
c
2
4
d
3
4
h
4
5
d
5
6
d
6
7
d
7
9
d
8
J
h
9
A
c
" ] }, "execution_count": 65, "metadata": {}, "output_type": "execute_result" } ], "source": [ "deck._Inner.OrderBy(c => c.Rank == \"A\" ? \"ZZ\" : c.Rank).Take(10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's try to re-order the collection to get two aces from the list of cards." ] }, { "cell_type": "code", "execution_count": 66, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
indexRankSuit
0
A
s
1
A
h
" ] }, "execution_count": 66, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// Order by descending value of rank, converting an Ace into a 'ZZ' for ordering so that it is the first item in the collection\n", "deck._Inner.OrderByDescending(c => c.Rank == \"A\" ? \"ZZ\" : c.Rank)\n", " .ThenByDescending(c => c.Suit) // Then order by the Suite\n", " .Take(2) // Take the first two items from the collection" ] } ], "metadata": { "kernelspec": { "display_name": ".NET (C#)", "language": "C#", "name": ".net-csharp" }, "language_info": { "file_extension": ".cs", "mimetype": "text/x-csharp", "name": "C#", "pygments_lexer": "csharp", "version": "8.0" } }, "nbformat": 4, "nbformat_minor": 4 }