From fdc946c28817aca932fc4d03cf2ac5c299ea7103 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 17 Jun 2020 15:03:18 -0400 Subject: [PATCH 1/9] first draft of code. --- notebooks/linq/index.ipynb | 230 +++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 notebooks/linq/index.ipynb diff --git a/notebooks/linq/index.ipynb b/notebooks/linq/index.ipynb new file mode 100644 index 0000000..3301243 --- /dev/null +++ b/notebooks/linq/index.ipynb @@ -0,0 +1,230 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This tutorial teaches you features in .NET Core and the C# language. You’ll learn how to:\n", + "\n", + "- Generate sequences with LINQ.\n", + "- Write methods that can be easily used in LINQ queries.\n", + "- Distinguish between eager and lazy evaluation.\n", + "\n", + "You'll learn these techniques by building an application that demonstrates one of the basic skills of any magician: the [faro shuffle](https://en.wikipedia.org/wiki/Faro_shuffle). Briefly, a faro shuffle is a technique where you split a card deck exactly in half, then the shuffle interleaves each one card from each half to rebuild the original deck.\n", + "\n", + "Magicians use this technique because every card is in a known location after each shuffle, and the order is a repeating pattern.\n", + "\n", + "For your purposes, it is a light hearted look at manipulating sequences of data. The application you'll build constructs a card deck and then performs a sequence of shuffles, writing the sequence out each time. You'll also compare the updated order to the original order.\n", + "\n", + "## Create a deck of cards\n", + "\n", + "Commonly, a deck of playing cards has four suits, and each suit has thirteen values. Let's create two `enum` types to represent the suits and the ranks, and two extension methods that enumerate each suit and each rank:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "public enum Suit\n", + " {\n", + " Clubs,\n", + " Diamonds,\n", + " Hearts,\n", + " Spades\n", + " }\n", + " \n", + "Console.WriteLine(\".NET Interactive knows about a Suit\");\n", + "\n", + " public enum Rank\n", + " {\n", + " Two,\n", + " Three,\n", + " Four,\n", + " Five,\n", + " Six,\n", + " Seven,\n", + " Eight,\n", + " Nine,\n", + " Ten,\n", + " Jack,\n", + " Queen,\n", + " King,\n", + " Ace\n", + " }\n", + " \n", + "Console.WriteLine(\".NET Interactive knows about a Face.\");\n", + "\n", + "static IEnumerable Suits() => Enum.GetValues(typeof(Suit)) as IEnumerable;\n", + "static IEnumerable Ranks() => Enum.GetValues(typeof(Rank)) as IEnumerable;\n", + "\n", + "Console.WriteLine(\".NET Interactive can generate suits and ranks.\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, define a `Deck` class to represent the deck of cards. This has two constructors. The default constructor generate a deck in the sorted order. The second constructor takes a sequence of cards in the specified order. Later, you'll use the second constructor as you shuffle the deck." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "public class Card\n", + "{\n", + " public Suit Suit { get; }\n", + " public Rank Rank { get; }\n", + "\n", + " public Card(Suit s, Rank r)\n", + " {\n", + " Suit = s;\n", + " Rank = r;\n", + " }\n", + "\n", + " public override string ToString() => $\"{Rank} of {Suit}\";\n", + "}\n", + "\n", + "public class Deck\n", + "{\n", + " private List cards = new List();\n", + " \n", + " public Deck()\n", + " {\n", + " var sequence = from s in Suits()\n", + " from r in Ranks()\n", + " select new Card(s, r);\n", + " cards.AddRange(sequence);\n", + " }\n", + " public Deck(IEnumerable cards) => this.cards = new List(cards);\n", + "\n", + " public IEnumerable Cards => cards;\n", + "}\n", + "\n", + "Console.WriteLine(\"The deck of cards is defined\");" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "Formatter.Register((deck, writer) =>\n", + "{\n", + " PocketView v = div(deck.Cards.Select(c =>\n", + " {\n", + " var face = Enum.GetName(typeof(Rank), c.Rank);\n", + " var suit = Enum.GetName(typeof(Suit), c.Suit);\n", + " return img[src:$\"https://dotnetcards.azureedge.net/cards/{suit}{face}.png\", width:64]();\n", + " }));\n", + " writer.Write(v);\n", + "}, \"text/html\");\n", + "\n", + "Console.WriteLine(\"card formatter defined\");" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "static IEnumerable InterleaveSequenceWith (this IEnumerable first, IEnumerable second)\n", + "{\n", + " var firstIter = first.GetEnumerator();\n", + " var secondIter = second.GetEnumerator();\n", + "\n", + " while (firstIter.MoveNext() && secondIter.MoveNext())\n", + " {\n", + " yield return firstIter.Current;\n", + " yield return secondIter.Current;\n", + " }\n", + "}\n", + "\n", + "Console.WriteLine(\"Shuffle method is defined\");" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "static bool SequenceEquals(this IEnumerable first, IEnumerable second)\n", + "{\n", + " var firstIter = first.GetEnumerator();\n", + " var secondIter = second.GetEnumerator();\n", + "\n", + " while (firstIter.MoveNext() && secondIter.MoveNext())\n", + " {\n", + " if (!firstIter.Current.Equals(secondIter.Current))\n", + " {\n", + " return false;\n", + " }\n", + " }\n", + "\n", + " return true;\n", + "}\n", + "Console.WriteLine(\"Sequence comparison method defined\");" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "var deck = new Deck();\n", + "var deckDisplay = display(deck);\n", + "int numberOfShuffles = 0;\n", + "var shuffleDisplay = display($\"Number of shuffles: {numberOfShuffles}\");" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "var shuffle = deck;\n", + "do\n", + "{\n", + " shuffle = new Deck(shuffle.Cards.Take(26).InterleaveSequenceWith(shuffle.Cards.Skip(26)));\n", + " numberOfShuffles++;\n", + " deckDisplay.Update(shuffle);\n", + " shuffleDisplay.Update($\"Number of shuffles: {numberOfShuffles}\");\n", + " await Task.Delay(3000);\n", + "} while(!deck.Cards.SequenceEquals(shuffle.Cards));\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "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 +} From 07bd1d7212cc7080e85e23a62857e10674a49f7b Mon Sep 17 00:00:00 2001 From: Diego Date: Fri, 19 Jun 2020 18:13:09 +0100 Subject: [PATCH 2/9] Update Dockerfile (#1) --- Dockerfile | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Dockerfile b/Dockerfile index 07bb640..a23c6ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM jupyter/scipy-notebook:latest +FROM jupyter/base-notebook:latest # Install .NET CLI dependencies @@ -14,40 +14,40 @@ USER root RUN apt-get update RUN apt-get install -y curl +ENV \ + # Enable detection of running in a container + DOTNET_RUNNING_IN_CONTAINER=true \ + # Enable correct mode for dotnet watch (only mode supported in a container) + DOTNET_USE_POLLING_FILE_WATCHER=true \ + # Skip extraction of XML docs - generally not useful within an image/container - helps performance + NUGET_XMLDOC_MODE=skip \ + # Opt out of telemetry until after we install jupyter when building the image, this prevents caching of machine id + DOTNET_TRY_CLI_TELEMETRY_OPTOUT=true + # Install .NET CLI dependencies -RUN apt-get install -y --no-install-recommends \ +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ libc6 \ libgcc1 \ libgssapi-krb5-2 \ - libicu60 \ + libicu66 \ libssl1.1 \ libstdc++6 \ - zlib1g - -RUN rm -rf /var/lib/apt/lists/* + zlib1g \ + && rm -rf /var/lib/apt/lists/* # Install .NET Core SDK - -# When updating the SDK version, the sha512 value a few lines down must also be updated. -ENV DOTNET_SDK_VERSION 3.1.200 - -RUN curl -SL --output dotnet.tar.gz https://dotnetcli.blob.core.windows.net/dotnet/Sdk/$DOTNET_SDK_VERSION/dotnet-sdk-$DOTNET_SDK_VERSION-linux-x64.tar.gz \ - && dotnet_sha512='5b9398c7bfe7f67cd9f38fdd4e6e429e1b6aaac0fe04672be0f8dca26580fb46906fd1d2deea6a7d3fb07d77e898f067d3ac1805fe077dc7c1adf9515c9bc9a9' \ +RUN dotnet_sdk_version=3.1.301 \ + && curl -SL --output dotnet.tar.gz https://dotnetcli.azureedge.net/dotnet/Sdk/$dotnet_sdk_version/dotnet-sdk-$dotnet_sdk_version-linux-x64.tar.gz \ + && dotnet_sha512='dd39931df438b8c1561f9a3bdb50f72372e29e5706d3fb4c490692f04a3d55f5acc0b46b8049bc7ea34dedba63c71b4c64c57032740cbea81eef1dce41929b4e' \ && echo "$dotnet_sha512 dotnet.tar.gz" | sha512sum -c - \ && mkdir -p /usr/share/dotnet \ - && tar -zxf dotnet.tar.gz -C /usr/share/dotnet \ + && tar -ozxf dotnet.tar.gz -C /usr/share/dotnet \ && rm dotnet.tar.gz \ - && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet - -# Enable detection of running in a container -ENV DOTNET_RUNNING_IN_CONTAINER=true \ - # Enable correct mode for dotnet watch (only mode supported in a container) - DOTNET_USE_POLLING_FILE_WATCHER=true \ - # Skip extraction of XML docs - generally not useful within an image/container - helps performance - NUGET_XMLDOC_MODE=skip \ - # Opt out of telemetry until after we install jupyter when building the image, this prevents caching of machine id - DOTNET_TRY_CLI_TELEMETRY_OPTOUT=true - + && ln -s /usr/share/dotnet/dotnet /usr/bin/dotnet \ + # Trigger first run experience by running arbitrary cmd + && dotnet help + # Copy notebooks COPY ./notebooks/ ${HOME}/Notebooks/ From 6d57ca45a83d40760b0157abf53782cf7b19d53c Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 19 Jun 2020 16:20:42 -0400 Subject: [PATCH 3/9] fix online display --- notebooks/linq/index.ipynb | 48 ++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/notebooks/linq/index.ipynb b/notebooks/linq/index.ipynb index 3301243..2411617 100644 --- a/notebooks/linq/index.ipynb +++ b/notebooks/linq/index.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -71,9 +71,11 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [] + } + ], "source": [ "\n", "public class Card\n", @@ -111,18 +113,20 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "Formatter.Register((deck, writer) =>\n", "{\n", - " PocketView v = div(deck.Cards.Select(c =>\n", - " {\n", - " var face = Enum.GetName(typeof(Rank), c.Rank);\n", - " var suit = Enum.GetName(typeof(Suit), c.Suit);\n", - " return img[src:$\"https://dotnetcards.azureedge.net/cards/{suit}{face}.png\", width:64]();\n", - " }));\n", + " PocketView v = div(\n", + " Enumerable.Range(0,4)\n", + " .Select(row => div(deck.Cards.Skip(row*13).Take(13).Select(c =>\n", + " {\n", + " var face = Enum.GetName(typeof(Rank), c.Rank);\n", + " var suit = Enum.GetName(typeof(Suit), c.Suit);\n", + " return img[src:$\"https://dotnetcards.azureedge.net/cards/{suit}{face}.png\", width:64]();\n", + " }))));\n", " writer.Write(v);\n", "}, \"text/html\");\n", "\n", @@ -131,7 +135,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -152,7 +156,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -176,40 +180,34 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "var deck = new Deck();\n", - "var deckDisplay = display(deck);\n", - "int numberOfShuffles = 0;\n", - "var shuffleDisplay = display($\"Number of shuffles: {numberOfShuffles}\");" + "var deckDisplay = display(deck);" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "var shuffle = deck;\n", + "int numberOfShuffles = 0;\n", + "var deckDisplay = display(deck);\n", + "var shuffleDisplay = display($\"Number of shuffles: {numberOfShuffles}\");\n", "do\n", "{\n", + " await Task.Delay(3000);\n", " shuffle = new Deck(shuffle.Cards.Take(26).InterleaveSequenceWith(shuffle.Cards.Skip(26)));\n", " numberOfShuffles++;\n", " deckDisplay.Update(shuffle);\n", " shuffleDisplay.Update($\"Number of shuffles: {numberOfShuffles}\");\n", - " await Task.Delay(3000);\n", "} while(!deck.Cards.SequenceEquals(shuffle.Cards));\n" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } ], "metadata": { "kernelspec": { From af595b13c0735676565bc586bfd44fb8dee2f101 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 19 Jun 2020 16:25:31 -0400 Subject: [PATCH 4/9] Revert "fix online display" This reverts commit ed7adfe84eec6db34c22397618f65a839b37b14b. --- notebooks/linq/index.ipynb | 48 ++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/notebooks/linq/index.ipynb b/notebooks/linq/index.ipynb index 2411617..3301243 100644 --- a/notebooks/linq/index.ipynb +++ b/notebooks/linq/index.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -71,11 +71,9 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 2, "metadata": {}, - "outputs": [] - } - ], + "outputs": [], "source": [ "\n", "public class Card\n", @@ -113,20 +111,18 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "Formatter.Register((deck, writer) =>\n", "{\n", - " PocketView v = div(\n", - " Enumerable.Range(0,4)\n", - " .Select(row => div(deck.Cards.Skip(row*13).Take(13).Select(c =>\n", - " {\n", - " var face = Enum.GetName(typeof(Rank), c.Rank);\n", - " var suit = Enum.GetName(typeof(Suit), c.Suit);\n", - " return img[src:$\"https://dotnetcards.azureedge.net/cards/{suit}{face}.png\", width:64]();\n", - " }))));\n", + " PocketView v = div(deck.Cards.Select(c =>\n", + " {\n", + " var face = Enum.GetName(typeof(Rank), c.Rank);\n", + " var suit = Enum.GetName(typeof(Suit), c.Suit);\n", + " return img[src:$\"https://dotnetcards.azureedge.net/cards/{suit}{face}.png\", width:64]();\n", + " }));\n", " writer.Write(v);\n", "}, \"text/html\");\n", "\n", @@ -135,7 +131,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -156,7 +152,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -180,34 +176,40 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "var deck = new Deck();\n", - "var deckDisplay = display(deck);" + "var deckDisplay = display(deck);\n", + "int numberOfShuffles = 0;\n", + "var shuffleDisplay = display($\"Number of shuffles: {numberOfShuffles}\");" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "var shuffle = deck;\n", - "int numberOfShuffles = 0;\n", - "var deckDisplay = display(deck);\n", - "var shuffleDisplay = display($\"Number of shuffles: {numberOfShuffles}\");\n", "do\n", "{\n", - " await Task.Delay(3000);\n", " shuffle = new Deck(shuffle.Cards.Take(26).InterleaveSequenceWith(shuffle.Cards.Skip(26)));\n", " numberOfShuffles++;\n", " deckDisplay.Update(shuffle);\n", " shuffleDisplay.Update($\"Number of shuffles: {numberOfShuffles}\");\n", + " await Task.Delay(3000);\n", "} while(!deck.Cards.SequenceEquals(shuffle.Cards));\n" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } ], "metadata": { "kernelspec": { From 0991ba8c487a78bcab0d8f81f38821e259778263 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 19 Jun 2020 16:27:34 -0400 Subject: [PATCH 5/9] fix JSON syntax --- notebooks/linq/index.ipynb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/notebooks/linq/index.ipynb b/notebooks/linq/index.ipynb index 3301243..1cef93f 100644 --- a/notebooks/linq/index.ipynb +++ b/notebooks/linq/index.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -71,7 +71,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -131,7 +131,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -152,7 +152,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -176,7 +176,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -188,7 +188,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ From 05789b2f7d4cbc1794789b6ca165178b6437f861 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 19 Jun 2020 16:41:56 -0400 Subject: [PATCH 6/9] fix formatter correctly --- notebooks/linq/index.ipynb | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/notebooks/linq/index.ipynb b/notebooks/linq/index.ipynb index 1cef93f..4b0bf53 100644 --- a/notebooks/linq/index.ipynb +++ b/notebooks/linq/index.ipynb @@ -117,12 +117,14 @@ "source": [ "Formatter.Register((deck, writer) =>\n", "{\n", - " PocketView v = div(deck.Cards.Select(c =>\n", - " {\n", - " var face = Enum.GetName(typeof(Rank), c.Rank);\n", - " var suit = Enum.GetName(typeof(Suit), c.Suit);\n", - " return img[src:$\"https://dotnetcards.azureedge.net/cards/{suit}{face}.png\", width:64]();\n", - " }));\n", + " PocketView v = div(\n", + " Enumerable.Range(0,4).Select(row =>\n", + " div(deck.Cards.Skip(row * 13).Take(13).Select(c =>\n", + " {\n", + " var face = Enum.GetName(typeof(Rank), c.Rank);\n", + " var suit = Enum.GetName(typeof(Suit), c.Suit);\n", + " return img[src:$\"https://dotnetcards.azureedge.net/cards/{suit}{face}.png\", width:64]();\n", + " }))));\n", " writer.Write(v);\n", "}, \"text/html\");\n", "\n", @@ -181,9 +183,7 @@ "outputs": [], "source": [ "var deck = new Deck();\n", - "var deckDisplay = display(deck);\n", - "int numberOfShuffles = 0;\n", - "var shuffleDisplay = display($\"Number of shuffles: {numberOfShuffles}\");" + "var deckDisplay = display(deck);" ] }, { @@ -192,24 +192,21 @@ "metadata": {}, "outputs": [], "source": [ + "int numberOfShuffles = 0;\n", + "var shuffleDisplay = display($\"Number of shuffles: {numberOfShuffles}\");\n", + "deckDisplay = display(deck);\n", "var shuffle = deck;\n", "do\n", "{\n", + " await Task.Delay(3000);\n", " shuffle = new Deck(shuffle.Cards.Take(26).InterleaveSequenceWith(shuffle.Cards.Skip(26)));\n", " numberOfShuffles++;\n", " deckDisplay.Update(shuffle);\n", " shuffleDisplay.Update($\"Number of shuffles: {numberOfShuffles}\");\n", - " await Task.Delay(3000);\n", - "} while(!deck.Cards.SequenceEquals(shuffle.Cards));\n" + "} while(!deck.Cards.SequenceEquals(shuffle.Cards));\n", + "Console.WriteLine(\"Done!\");\n" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } ], "metadata": { "kernelspec": { From 496ff997941c3856f4cda690f4237459e2c8893c Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 19 Jun 2020 16:45:12 -0400 Subject: [PATCH 7/9] fix JSON error --- notebooks/linq/index.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/linq/index.ipynb b/notebooks/linq/index.ipynb index 4b0bf53..47fef73 100644 --- a/notebooks/linq/index.ipynb +++ b/notebooks/linq/index.ipynb @@ -206,7 +206,7 @@ "} while(!deck.Cards.SequenceEquals(shuffle.Cards));\n", "Console.WriteLine(\"Done!\");\n" ] - }, + } ], "metadata": { "kernelspec": { From 5dd91148d8545be2b36aa5b2889cd6b02fd0aa2e Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 30 Jun 2020 17:37:50 -0400 Subject: [PATCH 8/9] add explanatory text Add text description for the lesson. --- notebooks/linq/index.ipynb | 184 +++++++++++++++++++++++++++++++------ 1 file changed, 158 insertions(+), 26 deletions(-) diff --git a/notebooks/linq/index.ipynb b/notebooks/linq/index.ipynb index 47fef73..d8bac01 100644 --- a/notebooks/linq/index.ipynb +++ b/notebooks/linq/index.ipynb @@ -4,6 +4,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "# Work with Language-Integrated Query (LINQ)\n", + "\n", "This tutorial teaches you features in .NET Core and the C# language. You’ll learn how to:\n", "\n", "- Generate sequences with LINQ.\n", @@ -23,7 +25,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -53,29 +55,23 @@ " King,\n", " Ace\n", " }\n", - " \n", - "Console.WriteLine(\".NET Interactive knows about a Face.\");\n", - "\n", - "static IEnumerable Suits() => Enum.GetValues(typeof(Suit)) as IEnumerable;\n", - "static IEnumerable Ranks() => Enum.GetValues(typeof(Rank)) as IEnumerable;\n", "\n", - "Console.WriteLine(\".NET Interactive can generate suits and ranks.\");" + "Console.WriteLine(\".NET Interactive knows about a Face.\");" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Next, define a `Deck` class to represent the deck of cards. This has two constructors. The default constructor generate a deck in the sorted order. The second constructor takes a sequence of cards in the specified order. Later, you'll use the second constructor as you shuffle the deck." + "A `Card` is defined by its `Suit` and its `Rank`:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ - "\n", "public class Card\n", "{\n", " public Suit Suit { get; }\n", @@ -90,28 +86,70 @@ " public override string ToString() => $\"{Rank} of {Suit}\";\n", "}\n", "\n", + "Console.WriteLine(\".NET Interactive knows about a Card.\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, define a `Deck` class to represent the deck of cards. First, define two [*iterator methods*](https://docs.microsoft.com/dotnet/csharp/iterators#enumeration-sources-with-iterator-methods) to generate the ranks and suits as sequences of values. These create the sequence of all suits and al ranks. They provide an easy way to enumerate all the possible combinations that make a deck of cards.\n", + "\n", + "The `Deck` class has two constructors. The default constructor generate a deck in the sorted order. The second constructor takes a sequence of cards in the specified order. Later, you'll use the second constructor as you shuffle the deck." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "static IEnumerable Suits() => Enum.GetValues(typeof(Suit)) as IEnumerable;\n", + "static IEnumerable Ranks() => Enum.GetValues(typeof(Rank)) as IEnumerable;\n", + "\n", + "Console.WriteLine(\".NET Interactive can generate suits and ranks.\");\n", + "\n", "public class Deck\n", "{\n", - " private List cards = new List();\n", + " private List cards;\n", " \n", " public Deck()\n", " {\n", " var sequence = from s in Suits()\n", - " from r in Ranks()\n", - " select new Card(s, r);\n", - " cards.AddRange(sequence);\n", + " from r in Ranks()\n", + " select new Card(s, r);\n", + " cards = new List(sequence);\n", " }\n", " public Deck(IEnumerable cards) => this.cards = new List(cards);\n", "\n", " public IEnumerable Cards => cards;\n", "}\n", "\n", - "Console.WriteLine(\"The deck of cards is defined\");" + "Console.WriteLine(\".NET Interactive knows about a Deck of cards\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The multiple `from` clauses produce a `SelectMany` call, which creates a single sequence from combining each element in the first sequence with each element in the second sequence. The order is important for our purposes. The first element in the first source sequence (Suits) is combined with every element in the second sequence (Ranks). This produces all thirteen cards of first suit. That process is repeated with each element in the first sequence (Suits). The end result is a deck of cards ordered by suits, followed by values.\n", + "\n", + "It's important to keep in mind that whether you choose to write your LINQ in the query syntax used above or use method syntax instead, it's always possible to go from one form of syntax to the other. The above query written in query syntax can be written in method syntax as:\n", + "\n", + "```csharp\n", + "var sequence = Suits().SelectMany(suit => Ranks().Select(rank => new Card(suit, rank));\n", + "```\n", + "\n", + "The compiler translates LINQ statements written with query syntax into the equivalent method call syntax. Therefore, regardless of your syntax choice, the two versions of the query produce the same result. Choose which syntax works best for your situation: for instance, if you're working in a team where some of the members have difficulty with method syntax, try to prefer using query syntax.\n", + "\n", + "## Display the deck\n", + "\n", + "Now that you've created the deck of cards, you can define a method that displays the deck in your notebook. The following method declares a formatter that displays the deck of cards in 4 rows of 13. It uses the method `Enumerable.Range` to count from 0 through 3. Then, is uses the `Skip` and `Take` methods to skip rows previously displayed then take the next 13 cards. The images are returned from a storage location on Azure." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -119,7 +157,7 @@ "{\n", " PocketView v = div(\n", " Enumerable.Range(0,4).Select(row =>\n", - " div(deck.Cards.Skip(row * 13).Take(13).Select(c =>\n", + " span(deck.Cards.Skip(row * 13).Take(13).Select(c =>\n", " {\n", " var face = Enum.GetName(typeof(Rank), c.Rank);\n", " var suit = Enum.GetName(typeof(Suit), c.Suit);\n", @@ -128,12 +166,40 @@ " writer.Write(v);\n", "}, \"text/html\");\n", "\n", - "Console.WriteLine(\"card formatter defined\");" + "Console.WriteLine(\".NET Interactive can display the deck of cards\");\n", + "\n", + "Deck d = new Deck();\n", + "display(d);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Shuffle the deck\n", + "\n", + "There's no shuffle method to take advantage of in the standard library, so you'll have to write your own. The shuffle method you'll be creating illustrates several techniques that you'll use with LINQ-based programs, so each part of this process will be explained in steps.\n", + "\n", + "The first step in any good shuffle is to split the deck in two. The `Take%` and `Skip` methods that are part of the LINQ APIs provide that feature for you.\n", + "\n", + "In order to add some functionality to how you interact with the `IEnumerable` sequence you'll get back from LINQ queries, you'll need to write some special kinds of methods called [extension methods](https://docs.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/extension-methods). Briefly, an extension method is a special purpose *static method* that adds new functionality to an already-existing type without having to modify the original type you want to add functionality to.\n", + "\n", + "Look at the method signature for a moment, specifically the parameters:\n", + "\n", + "```csharp\n", + "static IEnumerable InterleaveSequenceWith (this IEnumerable first, IEnumerable second)\n", + "```\n", + "\n", + "You can see the addition of the `this` modifier on the first argument to the method. That means you call the method as though it were a member method of the type of the first argument. This method declaration also follows a standard idiom where the input and output types are `IEnumerable`. That practice enables LINQ methods to be chained together to perform more complex queries.\n", + "\n", + "Naturally, since you split the deck into halves, you'll need to join those halves together. In code, this means you'll be enumerating both of the sequences you acquired through `Take` and `Skip` at once, *`interleaving`* the elements, and creating one sequence: your now-shuffled deck of cards. Writing a LINQ method that works with two sequences requires that you understand how `IEnumerable` works.\n", + "\n", + "The `System.Collections.Generic.IEnumerable` interface has one method: `GetEnumerator`. The object returned by `GetEnumerator` has a method to move to the next element, and a property that retrieves the current element in the sequence. You will use those two members to enumerate the collection and return the elements. This Interleave method will be an iterator method, so instead of building a collection and returning the collection, you'll use the `yield return` syntax shown above." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -149,12 +215,25 @@ " }\n", "}\n", "\n", - "Console.WriteLine(\"Shuffle method is defined\");" + "Console.WriteLine(\".NET Interactive can shuffle the deck\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Comparisons\n", + "\n", + "How many shuffles it takes to set the deck back to its original order? To find out, you'll need to write a method that determines if two sequences are equal. After you have that method, you'll need to place the code that shuffles the deck in a loop, and check to see when the deck is back in order.\n", + "\n", + "Writing a method to determine if the two sequences are equal should be straightforward. It's a similar structure to the method you wrote to shuffle the deck. Only this time, instead of `yield return`ing each element, you'll compare the matching elements of each sequence. When the entire sequence has been enumerated, if every element matches, the sequences are the same.\n", + "\n", + "This shows a second LINQ idiom: terminal methods. They take a sequence as input (or in this case, two sequences), and return a single scalar value. When using terminal methods, they are always the final method in a chain of methods for a LINQ query, hence the name \"terminal\"." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -173,22 +252,40 @@ "\n", " return true;\n", "}\n", - "Console.WriteLine(\"Sequence comparison method defined\");" + "Console.WriteLine(\".NET Interactive can compare sequences for equality.\");" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, + "source": [ + "You can see this in action when you use it to determine when the deck is back in its original order. First, create and display the deck:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "scrolled": false + }, "outputs": [], "source": [ "var deck = new Deck();\n", "var deckDisplay = display(deck);" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Put the shuffle code inside a loop, and stop when the sequence is back in its original order by applying the `SequenceEquals()` method. You can see it would always be the final method in any query, because it returns a single value instead of a sequence:\n", + "\n", + "Run the next cell and take note of how the deck rearranges on each shuffle. After 8 shuffles (iterations of the do-while loop), the deck returns to the original configuration it was in when you first created it from the starting LINQ query." + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -204,7 +301,42 @@ " deckDisplay.Update(shuffle);\n", " shuffleDisplay.Update($\"Number of shuffles: {numberOfShuffles}\");\n", "} while(!deck.Cards.SequenceEquals(shuffle.Cards));\n", - "Console.WriteLine(\"Done!\");\n" + "Console.WriteLine(\"Done!\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Experiment\n", + "\n", + "The sample you've built so far executes an *out shuffle*, where the top and bottom cards stay the same on each run. Let's make one change: we'll use an *in shuffle* instead, where all 52 cards change position. For an in shuffle, you interleave the deck so that the first card in the bottom half becomes the first card in the deck. That means the last card in the top half becomes the bottom card. This is a simple change to a singular line of code. Update the current shuffle query by switching the positions of `Take` and `Skip`. This will change the order of the top and bottom halves of the deck:\n", + "\n", + "```csharp\n", + "shuffle = shuffle.Skip(26).InterleaveSequenceWith(shuffle.Take(26));\n", + "```\n", + "\n", + "Run the previous cell again, and you'll see that it takes 52 iterations for the deck to reorder itself.\n", + "\n", + "## Conclusion\n", + "\n", + "In this project, you covered:\n", + "\n", + "- using LINQ queries to aggregate data into a meaningful sequence\n", + "- writing Extension methods to add our own custom functionality to LINQ queries\n", + "- locating areas in our code where our LINQ queries might run into performance issues like degraded speed\n", + "- lazy and eager evaluation in regards to LINQ queries and the implications they might have on query performance\n", + "\n", + "Aside from LINQ, you learned a bit about a technique magicians use for card tricks. Magicians use the Faro shuffle because they can control where every card moves in the deck. Now that you know, don't spoil it for everyone else!\n", + "\n", + "For more information on LINQ, see:\n", + "\n", + "- [Language Integrated Query (LINQ)](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/linq/index)\n", + "- [Introduction to LINQ](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/linq/index)\n", + "- [Basic LINQ Query Operations (C#)](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/linq/basic-linq-query-operations)\n", + "- [Data Transformations With LINQ (C#)](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/linq/data-transformations-with-linq)\n", + "- [Query Syntax and Method Syntax in LINQ (C#)](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/linq/query-syntax-and-method-syntax-in-linq)\n", + "- [C# Features That Support LINQ](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/linq/features-that-support-linq)\n" ] } ], From 3bd4e8519b11f601b0b9f850c1fbf309fa9c3314 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Wed, 1 Jul 2020 11:10:38 -0400 Subject: [PATCH 9/9] change span to div The div is needed to force a line break. --- notebooks/linq/index.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebooks/linq/index.ipynb b/notebooks/linq/index.ipynb index d8bac01..c3a7604 100644 --- a/notebooks/linq/index.ipynb +++ b/notebooks/linq/index.ipynb @@ -157,7 +157,7 @@ "{\n", " PocketView v = div(\n", " Enumerable.Range(0,4).Select(row =>\n", - " span(deck.Cards.Skip(row * 13).Take(13).Select(c =>\n", + " div(deck.Cards.Skip(row * 13).Take(13).Select(c =>\n", " {\n", " var face = Enum.GetName(typeof(Rank), c.Rank);\n", " var suit = Enum.GetName(typeof(Suit), c.Suit);\n",