diff --git a/vs4win/Chapter08/Ch08Ex02RegularExpressions/Ch08Ex02RegularExpressions.csproj b/vs4win/Chapter08/Ch08Ex02RegularExpressions/Ch08Ex02RegularExpressions.csproj
new file mode 100644
index 0000000..6431db6
--- /dev/null
+++ b/vs4win/Chapter08/Ch08Ex02RegularExpressions/Ch08Ex02RegularExpressions.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vs4win/Chapter08/Ch08Ex02RegularExpressions/Program.cs b/vs4win/Chapter08/Ch08Ex02RegularExpressions/Program.cs
new file mode 100644
index 0000000..118f186
--- /dev/null
+++ b/vs4win/Chapter08/Ch08Ex02RegularExpressions/Program.cs
@@ -0,0 +1,24 @@
+using System.Text.RegularExpressions;
+
+WriteLine("The default regular expression checks for at least one digit.");
+
+do
+{
+ Write("Enter a regular expression (or press ENTER to use the default): ");
+ string? regexp = ReadLine();
+
+ if (string.IsNullOrWhiteSpace(regexp))
+ {
+ regexp = @"^\d+$";
+ }
+
+ Write("Enter some input: ");
+ string input = ReadLine()!; // will never be null
+
+ Regex r = new(regexp);
+
+ WriteLine($"{input} matches {regexp}: {r.IsMatch(input)}");
+
+ WriteLine("Press ESC to end or any key to try again.");
+}
+while (ReadKey(intercept: true).Key != ConsoleKey.Escape);
\ No newline at end of file
diff --git a/vs4win/Chapter08/Ch08Ex03NumbersAsWordsApp/Ch08Ex03NumbersAsWordsApp.csproj b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsApp/Ch08Ex03NumbersAsWordsApp.csproj
new file mode 100644
index 0000000..c91be9d
--- /dev/null
+++ b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsApp/Ch08Ex03NumbersAsWordsApp.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vs4win/Chapter08/Ch08Ex03NumbersAsWordsApp/Program.cs b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsApp/Program.cs
new file mode 100644
index 0000000..7caac9e
--- /dev/null
+++ b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsApp/Program.cs
@@ -0,0 +1,16 @@
+using Packt.Shared; // ToWords extension method
+using System.Numerics; // BigInteger
+
+Write("Enter a number up to twenty one digits long: ");
+string? input = ReadLine();
+if (input is null) return;
+
+if (input.Length > 21)
+{
+ WriteLine("I cannot handle more than twenty one digits!");
+ return;
+}
+
+BigInteger number = BigInteger.Parse(input);
+
+WriteLine($"{number:N0} in words is {number.ToWords()}.");
\ No newline at end of file
diff --git a/vs4win/Chapter08/Ch08Ex03NumbersAsWordsLib/Ch08Ex03NumbersAsWordsLib.csproj b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsLib/Ch08Ex03NumbersAsWordsLib.csproj
new file mode 100644
index 0000000..9f5c4f4
--- /dev/null
+++ b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsLib/Ch08Ex03NumbersAsWordsLib.csproj
@@ -0,0 +1,7 @@
+
+
+
+ netstandard2.0
+
+
+
diff --git a/vs4win/Chapter08/Ch08Ex03NumbersAsWordsLib/NumbersToWords.cs b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsLib/NumbersToWords.cs
new file mode 100644
index 0000000..5ea0834
--- /dev/null
+++ b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsLib/NumbersToWords.cs
@@ -0,0 +1,220 @@
+using System.Diagnostics; // Trace
+using System.IO; // File
+using System.Numerics; // BigInteger
+
+namespace Packt.Shared
+{
+ public static class NumbersToWords
+ {
+ // Single-digit and small number names
+ private static string[] smallNumbers = new string[]
+ {
+ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight",
+ "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
+ "sixteen", "seventeen", "eighteen", "nineteen"
+ };
+
+ // Tens number names from twenty upwards
+ private static string[] tens = new string[]
+ {
+ "", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy",
+ "eighty", "ninety"
+ };
+
+ // Scale number names for use during recombination
+ private static string[] scaleNumbers = new string[]
+ {
+ "", "thousand", "million", "billion", "trillion",
+ "quadrillion", "quintillion"
+ };
+
+ private static int groups = 7; // i.e. up to quintillion
+
+ public static string ToWords(this int number)
+ {
+ return ToWords((BigInteger)number);
+ }
+
+ public static string ToWords(this long number)
+ {
+ return ToWords((BigInteger)number);
+ }
+
+ public static string ToWords(this BigInteger number)
+ {
+ /*
+ Convert A Number into Words
+ by Richard Carr, published at http://www.blackwasp.co.uk/numbertowords.aspx
+ */
+
+ /*
+ Zero Rule.
+ If the value is 0 then the number in words is 'zero' and no other rules apply.
+ */
+ if (number == 0)
+ {
+ return "zero";
+ }
+
+ /*
+ Three Digit Rule.
+ The integer value is split into groups of three digits starting from the
+ right-hand side. Each set of three digits is then processed individually
+ as a number of hundreds, tens and units. Once converted to text, the
+ three-digit groups are recombined with the addition of the relevant scale
+ number (thousand, million, billion).
+ */
+
+ // Array to hold the specified number of three-digit groups
+ int[] digitGroups = new int[groups];
+
+ // Ensure a positive number to extract from
+ var positive = BigInteger.Abs(number);
+
+ // Extract the three-digit groups
+ for (int i = 0; i < groups; i++)
+ {
+ digitGroups[i] = (int)(positive % 1000);
+ positive /= 1000;
+ }
+
+ // write to a text file in the project folder
+ Trace.Listeners.Add(new TextWriterTraceListener(
+ File.AppendText("log.txt")));
+
+ // text writer is buffered, so this option calls
+ // Flush() on all listeners after writing
+ Trace.AutoFlush = true;
+
+ // log array of group numbers
+ for (int x = 0; x < digitGroups.Length; x++)
+ {
+ Trace.WriteLine(string.Format(
+ format: "digitGroups[{0}] = {1}",
+ arg0: x,
+ arg1: digitGroups[x]));
+ }
+
+ // Convert each three-digit group to words
+ string[] groupTexts = new string[groups];
+
+ for (int i = 0; i < groups; i++)
+ {
+ // call a local function (see below)
+ groupTexts[i] = ThreeDigitGroupToWords(digitGroups[i]);
+ }
+
+ // log array of group texts
+ for (int x = 0; x < groupTexts.Length; x++)
+ {
+ Trace.WriteLine(string.Format(
+ format: "groupTexts[{0}] = {1}",
+ arg0: x,
+ arg1: groupTexts[x]));
+ }
+
+ /*
+ Recombination Rules.
+ When recombining the translated three-digit groups, each group except the
+ last is followed by a large number name and a comma, unless the group is
+ blank and therefore not included at all. One exception is when the final
+ group does not include any hundreds and there is more than one non-blank
+ group. In this case, the final comma is replaced with 'and'. eg.
+ 'one billion, one million and twelve'.
+ */
+
+ // Recombine the three-digit groups
+ string combined = groupTexts[0];
+ bool appendAnd;
+
+ // Determine whether an 'and' is needed
+ appendAnd = (digitGroups[0] > 0) && (digitGroups[0] < 100);
+
+ // Process the remaining groups in turn, smallest to largest
+ for (int i = 1; i < groups; i++)
+ {
+ // Only add non-zero items
+ if (digitGroups[i] != 0)
+ {
+ // Build the string to add as a prefix
+ string prefix = groupTexts[i] + " " + scaleNumbers[i];
+
+ if (combined.Length != 0)
+ {
+ prefix += appendAnd ? " and " : ", ";
+ }
+
+ // Opportunity to add 'and' is ended
+ appendAnd = false;
+
+ // Add the three-digit group to the combined string
+ combined = prefix + combined;
+ }
+ }
+
+ // Converts a three-digit group into English words
+ string ThreeDigitGroupToWords(int threeDigits)
+ {
+ // Initialise the return text
+ string groupText = "";
+
+ // Determine the hundreds and the remainder
+ int hundreds = threeDigits / 100;
+ int tensUnits = threeDigits % 100;
+
+ /*
+ Hundreds Rules.
+ If the hundreds portion of a three-digit group is not zero, the number of
+ hundreds is added as a word. If the three-digit group is exactly divisible
+ by one hundred, the text 'hundred' is appended. If not, the text
+ "hundred and" is appended. eg. 'two hundred' or 'one hundred and twelve'
+ */
+
+ if (hundreds != 0)
+ {
+ groupText += smallNumbers[hundreds] + " hundred";
+
+ if (tensUnits != 0)
+ {
+ groupText += " and ";
+ }
+ }
+
+ // Determine the tens and units
+ int tens = tensUnits / 10;
+ int units = tensUnits % 10;
+
+ /* Tens Rules.
+ If the tens section of a three-digit group is two or higher, the appropriate
+ '-ty' word (twenty, thirty, etc.) is added to the text and followed by the
+ name of the third digit (unless the third digit is a zero, which is ignored).
+ If the tens and the units are both zero, no text is added. For any other value,
+ the name of the one or two-digit number is added as a special case.
+ */
+
+ if (tens >= 2)
+ {
+ groupText += NumbersToWords.tens[tens];
+ if (units != 0)
+ {
+ groupText += " " + smallNumbers[units];
+ }
+ }
+ else if (tensUnits != 0)
+ groupText += smallNumbers[tensUnits];
+
+ return groupText;
+ }
+
+ /* Negative Rule.
+ Negative numbers are always preceded by the text 'negative'.
+ */
+ if (number < 0)
+ {
+ combined = "negative " + combined;
+ }
+
+ return combined;
+ }
+ }
+}
diff --git a/vs4win/Chapter08/Ch08Ex03NumbersAsWordsTests/Ch08Ex03NumbersAsWordsTests.csproj b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsTests/Ch08Ex03NumbersAsWordsTests.csproj
new file mode 100644
index 0000000..de9c08a
--- /dev/null
+++ b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsTests/Ch08Ex03NumbersAsWordsTests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/vs4win/Chapter08/Ch08Ex03NumbersAsWordsTests/NumbersToWordsUnitTests.cs b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsTests/NumbersToWordsUnitTests.cs
new file mode 100644
index 0000000..9a386df
--- /dev/null
+++ b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsTests/NumbersToWordsUnitTests.cs
@@ -0,0 +1,64 @@
+using Packt.Shared; // ToWords extension method
+using System.Numerics; // BigInteger
+
+namespace Ch08Ex03NumbersAsWordsTests
+{
+ public class NumbersToWordsUnitTests
+ {
+ [Fact]
+ public void Test_Int32_0()
+ {
+ // arrange
+ int number = 0;
+ string expected = "zero";
+
+ // act
+ string actual = number.ToWords();
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void Test_Int32_1234()
+ {
+ // arrange
+ int number = 1234;
+ string expected = "one thousand, two hundred and thirty four";
+
+ // act
+ string actual = number.ToWords();
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void Test_BigInteger_18456002032011000007()
+ {
+ // arrange
+ BigInteger number = BigInteger.Parse("18456002032011000007");
+ string expected = "eighteen quintillion, four hundred and fifty six quadrillion, two trillion, thirty two billion, eleven million and seven";
+
+ // act
+ string actual = number.ToWords();
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void Test_Int32_minus_13()
+ {
+ // arrange
+ int number = -13;
+ string expected = "negative thirteen";
+
+ // act
+ string actual = number.ToWords();
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+ }
+}
\ No newline at end of file
diff --git a/vs4win/Chapter08/Ch08Ex03NumbersAsWordsTests/Usings.cs b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsTests/Usings.cs
new file mode 100644
index 0000000..8c927eb
--- /dev/null
+++ b/vs4win/Chapter08/Ch08Ex03NumbersAsWordsTests/Usings.cs
@@ -0,0 +1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/vs4win/Chapter08/Chapter08.sln b/vs4win/Chapter08/Chapter08.sln
new file mode 100644
index 0000000..efa8191
--- /dev/null
+++ b/vs4win/Chapter08/Chapter08.sln
@@ -0,0 +1,79 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32210.308
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkingWithNumbers", "WorkingWithNumbers\WorkingWithNumbers.csproj", "{EF42A2EE-FB4A-4BE4-A331-2B3D6CC1A1C5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkingWithText", "WorkingWithText\WorkingWithText.csproj", "{BD0568D2-A0C9-4FB5-AD3A-A685E794A29E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkingWithRegularExpressions", "WorkingWithRegularExpressions\WorkingWithRegularExpressions.csproj", "{221EF67C-EB18-417F-811F-2836BBC4B9ED}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkingWithCollections", "WorkingWithCollections\WorkingWithCollections.csproj", "{678CA866-8E00-48EB-A8F5-1D587BFB311A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkingWithRanges", "WorkingWithRanges\WorkingWithRanges.csproj", "{D93AB524-D62D-422B-895A-B1F0ACD3E137}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkingWithNetworkResources", "WorkingWithNetworkResources\WorkingWithNetworkResources.csproj", "{5A9BBCB8-F680-45DA-BBAD-DC20F3FBEE4E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ch08Ex02RegularExpressions", "Ch08Ex02RegularExpressions\Ch08Ex02RegularExpressions.csproj", "{BDCF7297-FA66-437C-A428-AF1B32C520DD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ch08Ex03NumbersAsWordsLib", "Ch08Ex03NumbersAsWordsLib\Ch08Ex03NumbersAsWordsLib.csproj", "{42F893F1-B861-4B4B-A14B-4F278E11F87F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ch08Ex03NumbersAsWordsApp", "Ch08Ex03NumbersAsWordsApp\Ch08Ex03NumbersAsWordsApp.csproj", "{25385931-197B-4713-82B9-74764DBD76A3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ch08Ex03NumbersAsWordsTests", "Ch08Ex03NumbersAsWordsTests\Ch08Ex03NumbersAsWordsTests.csproj", "{793E6479-9AE4-4802-B5BF-BC4CED5E72F0}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {EF42A2EE-FB4A-4BE4-A331-2B3D6CC1A1C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EF42A2EE-FB4A-4BE4-A331-2B3D6CC1A1C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EF42A2EE-FB4A-4BE4-A331-2B3D6CC1A1C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EF42A2EE-FB4A-4BE4-A331-2B3D6CC1A1C5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BD0568D2-A0C9-4FB5-AD3A-A685E794A29E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BD0568D2-A0C9-4FB5-AD3A-A685E794A29E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BD0568D2-A0C9-4FB5-AD3A-A685E794A29E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BD0568D2-A0C9-4FB5-AD3A-A685E794A29E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {221EF67C-EB18-417F-811F-2836BBC4B9ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {221EF67C-EB18-417F-811F-2836BBC4B9ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {221EF67C-EB18-417F-811F-2836BBC4B9ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {221EF67C-EB18-417F-811F-2836BBC4B9ED}.Release|Any CPU.Build.0 = Release|Any CPU
+ {678CA866-8E00-48EB-A8F5-1D587BFB311A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {678CA866-8E00-48EB-A8F5-1D587BFB311A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {678CA866-8E00-48EB-A8F5-1D587BFB311A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {678CA866-8E00-48EB-A8F5-1D587BFB311A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D93AB524-D62D-422B-895A-B1F0ACD3E137}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D93AB524-D62D-422B-895A-B1F0ACD3E137}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D93AB524-D62D-422B-895A-B1F0ACD3E137}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D93AB524-D62D-422B-895A-B1F0ACD3E137}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5A9BBCB8-F680-45DA-BBAD-DC20F3FBEE4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5A9BBCB8-F680-45DA-BBAD-DC20F3FBEE4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5A9BBCB8-F680-45DA-BBAD-DC20F3FBEE4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5A9BBCB8-F680-45DA-BBAD-DC20F3FBEE4E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BDCF7297-FA66-437C-A428-AF1B32C520DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BDCF7297-FA66-437C-A428-AF1B32C520DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BDCF7297-FA66-437C-A428-AF1B32C520DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BDCF7297-FA66-437C-A428-AF1B32C520DD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {42F893F1-B861-4B4B-A14B-4F278E11F87F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {42F893F1-B861-4B4B-A14B-4F278E11F87F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {42F893F1-B861-4B4B-A14B-4F278E11F87F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {42F893F1-B861-4B4B-A14B-4F278E11F87F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {25385931-197B-4713-82B9-74764DBD76A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {25385931-197B-4713-82B9-74764DBD76A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {25385931-197B-4713-82B9-74764DBD76A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {25385931-197B-4713-82B9-74764DBD76A3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {793E6479-9AE4-4802-B5BF-BC4CED5E72F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {793E6479-9AE4-4802-B5BF-BC4CED5E72F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {793E6479-9AE4-4802-B5BF-BC4CED5E72F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {793E6479-9AE4-4802-B5BF-BC4CED5E72F0}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {BD4295A9-D6CF-470E-8220-CDA9A1FEA2C6}
+ EndGlobalSection
+EndGlobal
diff --git a/vs4win/Chapter08/WorkingWithCollections/Program.Helpers.cs b/vs4win/Chapter08/WorkingWithCollections/Program.Helpers.cs
new file mode 100644
index 0000000..a31041c
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithCollections/Program.Helpers.cs
@@ -0,0 +1,21 @@
+partial class Program
+{
+ static void Output(string title, IEnumerable collection)
+ {
+ WriteLine(title);
+ foreach (string item in collection)
+ {
+ WriteLine($" {item}");
+ }
+ }
+
+ static void OutputPQ(string title,
+ IEnumerable<(TElement Element, TPriority Priority)> collection)
+ {
+ WriteLine(title);
+ foreach ((TElement, TPriority) item in collection)
+ {
+ WriteLine($" {item.Item1}: {item.Item2}");
+ }
+ }
+}
diff --git a/vs4win/Chapter08/WorkingWithCollections/Program.cs b/vs4win/Chapter08/WorkingWithCollections/Program.cs
new file mode 100644
index 0000000..599e08e
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithCollections/Program.cs
@@ -0,0 +1,129 @@
+using System.Collections.Immutable;
+
+// Working with lists
+
+// Simple syntax for creating a list and adding three items
+List cities = new();
+cities.Add("London");
+cities.Add("Paris");
+cities.Add("Milan");
+
+/* Alternative syntax that is converted by the compiler into
+ the three Add method calls above
+List cities = new()
+ { "London", "Paris", "Milan" }; */
+
+/* Alternative syntax that passes an
+ array of string values to AddRange method
+List cities = new();
+cities.AddRange(new[] { "London", "Paris", "Milan" }); */
+
+Output("Initial list", cities);
+WriteLine($"The first city is {cities[0]}.");
+WriteLine($"The last city is {cities[cities.Count - 1]}.");
+
+cities.Insert(0, "Sydney");
+Output("After inserting Sydney at index 0", cities);
+
+cities.RemoveAt(1);
+cities.Remove("Milan");
+Output("After removing two cities", cities);
+
+// Working with dictionaries
+
+Dictionary keywords = new();
+
+// add using named parameters
+keywords.Add(key: "int", value: "32-bit integer data type");
+
+// add using positional parameters
+keywords.Add("long", "64-bit integer data type");
+keywords.Add("float", "Single precision floating point number");
+
+/* Alternative syntax; compiler converts this to calls to Add method
+Dictionary keywords = new()
+{
+ { "int", "32-bit integer data type" },
+ { "long", "64-bit integer data type" },
+ { "float", "Single precision floating point number" },
+}; */
+
+/* Alternative syntax; compiler converts this to calls to Add method
+Dictionary keywords = new()
+{
+ ["int"] = "32-bit integer data type",
+ ["long"] = "64-bit integer data type",
+ ["float"] = "Single precision floating point number", // last comma is optional
+}; */
+
+Output("Dictionary keys:", keywords.Keys);
+Output("Dictionary values:", keywords.Values);
+
+WriteLine("Keywords and their definitions");
+foreach (KeyValuePair item in keywords)
+{
+ WriteLine($" {item.Key}: {item.Value}");
+}
+
+// lookup a value using a key
+string key = "long";
+WriteLine($"The definition of {key} is {keywords[key]}");
+
+// Working with queues
+
+Queue coffee = new();
+
+coffee.Enqueue("Damir"); // front of queue
+coffee.Enqueue("Andrea");
+coffee.Enqueue("Ronald");
+coffee.Enqueue("Amin");
+coffee.Enqueue("Irina"); // back of queue
+
+Output("Initial queue from front to back", coffee);
+
+// server handles next person in queue
+string served = coffee.Dequeue();
+WriteLine($"Served: {served}.");
+
+// server handles next person in queue
+served = coffee.Dequeue();
+WriteLine($"Served: {served}.");
+Output("Current queue from front to back", coffee);
+
+WriteLine($"{coffee.Peek()} is next in line.");
+Output("Current queue from front to back", coffee);
+
+// Working with priority queues
+
+PriorityQueue vaccine = new();
+
+// add some people
+// 1 = high priority people in their 70s or poor health
+// 2 = medium priority e.g. middle aged
+// 3 = low priority e.g. teens and twenties
+
+vaccine.Enqueue("Pamela", 1); // my mum (70s)
+vaccine.Enqueue("Rebecca", 3); // my niece (teens)
+vaccine.Enqueue("Juliet", 2); // my sister (40s)
+vaccine.Enqueue("Ian", 1); // my dad (70s)
+
+OutputPQ("Current queue for vaccination:", vaccine.UnorderedItems);
+
+WriteLine($"{vaccine.Dequeue()} has been vaccinated.");
+WriteLine($"{vaccine.Dequeue()} has been vaccinated.");
+OutputPQ("Current queue for vaccination:", vaccine.UnorderedItems);
+
+WriteLine($"{vaccine.Dequeue()} has been vaccinated.");
+
+WriteLine("Adding Mark to queue with priority 2");
+vaccine.Enqueue("Mark", 2); // me (50s)
+
+WriteLine($"{vaccine.Peek()} will be next to be vaccinated.");
+OutputPQ("Current queue for vaccination:", vaccine.UnorderedItems);
+
+// Working with immutable collections
+
+ImmutableList immutableCities = cities.ToImmutableList();
+ImmutableList newList = immutableCities.Add("Rio");
+Output("Immutable list of cities:", immutableCities);
+Output("New list of cities:", newList);
diff --git a/vs4win/Chapter08/WorkingWithCollections/WorkingWithCollections.csproj b/vs4win/Chapter08/WorkingWithCollections/WorkingWithCollections.csproj
new file mode 100644
index 0000000..cd63b28
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithCollections/WorkingWithCollections.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vs4win/Chapter08/WorkingWithNetworkResources/Program.cs b/vs4win/Chapter08/WorkingWithNetworkResources/Program.cs
new file mode 100644
index 0000000..e614734
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithNetworkResources/Program.cs
@@ -0,0 +1,50 @@
+using System.Net; // IPHostEntry, Dns, IPAddress
+using System.Net.NetworkInformation; // Ping, PingReply, IPStatus
+
+// Working with URIs, DNS, and IP addresses
+
+Write("Enter a valid web address (or press Enter): ");
+string? url = ReadLine();
+
+if (string.IsNullOrWhiteSpace(url)) // if they enter nothing...
+{
+ // ... set a default URL
+ url = "https://stackoverflow.com/search?q=securestring";
+}
+
+Uri uri = new(url);
+WriteLine($"URL: {url}");
+WriteLine($"Scheme: {uri.Scheme}");
+WriteLine($"Port: {uri.Port}");
+WriteLine($"Host: {uri.Host}");
+WriteLine($"Path: {uri.AbsolutePath}");
+WriteLine($"Query: {uri.Query}");
+
+IPHostEntry entry = Dns.GetHostEntry(uri.Host);
+WriteLine($"{entry.HostName} has the following IP addresses:");
+foreach (IPAddress address in entry.AddressList)
+{
+ WriteLine($" {address} ({address.AddressFamily})");
+}
+
+// Pinging a server
+
+try
+{
+ Ping ping = new();
+
+ WriteLine("Pinging server. Please wait...");
+ PingReply reply = ping.Send(uri.Host);
+ WriteLine($"{uri.Host} was pinged and replied: {reply.Status}.");
+
+ if (reply.Status == IPStatus.Success)
+ {
+ WriteLine("Reply from {0} took {1:N0}ms",
+ arg0: reply.Address,
+ arg1: reply.RoundtripTime);
+ }
+}
+catch (Exception ex)
+{
+ WriteLine($"{ex.GetType().ToString()} says {ex.Message}");
+}
diff --git a/vs4win/Chapter08/WorkingWithNetworkResources/WorkingWithNetworkResources.csproj b/vs4win/Chapter08/WorkingWithNetworkResources/WorkingWithNetworkResources.csproj
new file mode 100644
index 0000000..91807b2
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithNetworkResources/WorkingWithNetworkResources.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vs4win/Chapter08/WorkingWithNumbers/Program.cs b/vs4win/Chapter08/WorkingWithNumbers/Program.cs
new file mode 100644
index 0000000..7985a1e
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithNumbers/Program.cs
@@ -0,0 +1,27 @@
+using System.Numerics;
+
+WriteLine("Working with large integers:");
+WriteLine("-----------------------------------");
+
+ulong big = ulong.MaxValue;
+WriteLine($"{big,40:N0}");
+
+BigInteger bigger =
+ BigInteger.Parse("123456789012345678901234567890");
+WriteLine($"{bigger,40:N0}");
+
+WriteLine("Working with complex numbers:");
+
+Complex c1 = new(real: 4, imaginary: 2);
+Complex c2 = new(real: 3, imaginary: 7);
+Complex c3 = c1 + c2;
+
+// output using default ToString implementation
+WriteLine($"{c1} added to {c2} is {c3}");
+
+// output using custom format
+WriteLine("{0} + {1}i added to {2} + {3}i is {4} + {5}i",
+ c1.Real, c1.Imaginary,
+ c2.Real, c2.Imaginary,
+ c3.Real, c3.Imaginary);
+
diff --git a/vs4win/Chapter08/WorkingWithNumbers/WorkingWithNumbers.csproj b/vs4win/Chapter08/WorkingWithNumbers/WorkingWithNumbers.csproj
new file mode 100644
index 0000000..cd63b28
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithNumbers/WorkingWithNumbers.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vs4win/Chapter08/WorkingWithRanges/Program.cs b/vs4win/Chapter08/WorkingWithRanges/Program.cs
new file mode 100644
index 0000000..584461f
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithRanges/Program.cs
@@ -0,0 +1,28 @@
+string name = "Samantha Jones";
+
+// getting the lengths of the first and last names
+
+int lengthOfFirst = name.IndexOf(' ');
+int lengthOfLast = name.Length - lengthOfFirst - 1;
+
+// Using Substring
+
+string firstName = name.Substring(
+ startIndex: 0,
+ length: lengthOfFirst);
+
+string lastName = name.Substring(
+ startIndex: name.Length - lengthOfLast,
+ length: lengthOfLast);
+
+WriteLine($"First name: {firstName}, Last name: {lastName}");
+
+// Using spans
+
+ReadOnlySpan nameAsSpan = name.AsSpan();
+ReadOnlySpan firstNameSpan = nameAsSpan[0..lengthOfFirst];
+ReadOnlySpan lastNameSpan = nameAsSpan[^lengthOfLast..^0];
+
+WriteLine("First name: {0}, Last name: {1}",
+ arg0: firstNameSpan.ToString(),
+ arg1: lastNameSpan.ToString());
diff --git a/vs4win/Chapter08/WorkingWithRanges/WorkingWithRanges.csproj b/vs4win/Chapter08/WorkingWithRanges/WorkingWithRanges.csproj
new file mode 100644
index 0000000..cd63b28
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithRanges/WorkingWithRanges.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vs4win/Chapter08/WorkingWithRegularExpressions/Program.cs b/vs4win/Chapter08/WorkingWithRegularExpressions/Program.cs
new file mode 100644
index 0000000..434bbd8
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithRegularExpressions/Program.cs
@@ -0,0 +1,38 @@
+using System.Text.RegularExpressions; // Regex
+
+Write("Enter your age: ");
+string input = ReadLine()!; // null-forgiving
+
+Regex ageChecker = new(@"^\d+$");
+
+if (ageChecker.IsMatch(input))
+{
+ WriteLine("Thank you!");
+}
+else
+{
+ WriteLine($"This is not a valid age: {input}");
+}
+
+string films = "\"Monsters, Inc.\",\"I, Tonya\",\"Lock, Stock and Two Smoking Barrels\"";
+
+WriteLine($"Films to split: {films}");
+
+string[] filmsDumb = films.Split(',');
+
+WriteLine("Splitting with string.Split method:");
+foreach (string film in filmsDumb)
+{
+ WriteLine(film);
+}
+
+Regex csv = new(
+ "(?:^|,)(?=[^\"]|(\")?)\"?((?(1)[^\"]*|[^,\"]*))\"?(?=,|$)");
+
+MatchCollection filmsSmart = csv.Matches(films);
+
+WriteLine("Splitting with regular expression:");
+foreach (Match film in filmsSmart)
+{
+ WriteLine(film.Groups[2].Value);
+}
diff --git a/vs4win/Chapter08/WorkingWithRegularExpressions/WorkingWithRegularExpressions.csproj b/vs4win/Chapter08/WorkingWithRegularExpressions/WorkingWithRegularExpressions.csproj
new file mode 100644
index 0000000..cd63b28
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithRegularExpressions/WorkingWithRegularExpressions.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vs4win/Chapter08/WorkingWithText/Program.cs b/vs4win/Chapter08/WorkingWithText/Program.cs
new file mode 100644
index 0000000..139e2ed
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithText/Program.cs
@@ -0,0 +1,45 @@
+string city = "London";
+WriteLine($"{city} is {city.Length} characters long.");
+
+WriteLine($"First char is {city[0]} and third is {city[2]}.");
+
+string cities = "Paris,Tehran,Chennai,Sydney,New York,Medellín";
+
+string[] citiesArray = cities.Split(',');
+
+WriteLine($"There are {citiesArray.Length} items in the array.");
+
+foreach (string item in citiesArray)
+{
+ WriteLine(item);
+}
+
+string fullName = "Alan Jones";
+
+int indexOfTheSpace = fullName.IndexOf(' ');
+
+string firstName = fullName.Substring(
+ startIndex: 0, length: indexOfTheSpace);
+
+string lastName = fullName.Substring(
+ startIndex: indexOfTheSpace + 1);
+
+WriteLine($"Original: {fullName}");
+WriteLine($"Swapped: {lastName}, {firstName}");
+
+string company = "Microsoft";
+bool startsWithM = company.StartsWith("M");
+bool containsN = company.Contains("N");
+WriteLine($"Text: {company}");
+WriteLine($"Starts with M: {startsWithM}, contains an N: {containsN}");
+
+string recombined = string.Join(" => ", citiesArray);
+WriteLine(recombined);
+
+string fruit = "Apples";
+decimal price = 0.39M;
+DateTime when = DateTime.Today;
+
+WriteLine($"Interpolated: {fruit} cost {price:C} on {when:dddd}.");
+WriteLine(string.Format("string.Format: {0} cost {1:C} on {2:dddd}.",
+ arg0: fruit, arg1: price, arg2: when));
diff --git a/vs4win/Chapter08/WorkingWithText/WorkingWithText.csproj b/vs4win/Chapter08/WorkingWithText/WorkingWithText.csproj
new file mode 100644
index 0000000..cd63b28
--- /dev/null
+++ b/vs4win/Chapter08/WorkingWithText/WorkingWithText.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vscode/Chapter08/Ch08Ex02RegularExpressions/Ch08Ex02RegularExpressions.csproj b/vscode/Chapter08/Ch08Ex02RegularExpressions/Ch08Ex02RegularExpressions.csproj
new file mode 100644
index 0000000..6431db6
--- /dev/null
+++ b/vscode/Chapter08/Ch08Ex02RegularExpressions/Ch08Ex02RegularExpressions.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vscode/Chapter08/Ch08Ex02RegularExpressions/Program.cs b/vscode/Chapter08/Ch08Ex02RegularExpressions/Program.cs
new file mode 100644
index 0000000..118f186
--- /dev/null
+++ b/vscode/Chapter08/Ch08Ex02RegularExpressions/Program.cs
@@ -0,0 +1,24 @@
+using System.Text.RegularExpressions;
+
+WriteLine("The default regular expression checks for at least one digit.");
+
+do
+{
+ Write("Enter a regular expression (or press ENTER to use the default): ");
+ string? regexp = ReadLine();
+
+ if (string.IsNullOrWhiteSpace(regexp))
+ {
+ regexp = @"^\d+$";
+ }
+
+ Write("Enter some input: ");
+ string input = ReadLine()!; // will never be null
+
+ Regex r = new(regexp);
+
+ WriteLine($"{input} matches {regexp}: {r.IsMatch(input)}");
+
+ WriteLine("Press ESC to end or any key to try again.");
+}
+while (ReadKey(intercept: true).Key != ConsoleKey.Escape);
\ No newline at end of file
diff --git a/vscode/Chapter08/Ch08Ex03NumbersAsWordsApp/Ch08Ex03NumbersAsWordsApp.csproj b/vscode/Chapter08/Ch08Ex03NumbersAsWordsApp/Ch08Ex03NumbersAsWordsApp.csproj
new file mode 100644
index 0000000..c91be9d
--- /dev/null
+++ b/vscode/Chapter08/Ch08Ex03NumbersAsWordsApp/Ch08Ex03NumbersAsWordsApp.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vscode/Chapter08/Ch08Ex03NumbersAsWordsApp/Program.cs b/vscode/Chapter08/Ch08Ex03NumbersAsWordsApp/Program.cs
new file mode 100644
index 0000000..7caac9e
--- /dev/null
+++ b/vscode/Chapter08/Ch08Ex03NumbersAsWordsApp/Program.cs
@@ -0,0 +1,16 @@
+using Packt.Shared; // ToWords extension method
+using System.Numerics; // BigInteger
+
+Write("Enter a number up to twenty one digits long: ");
+string? input = ReadLine();
+if (input is null) return;
+
+if (input.Length > 21)
+{
+ WriteLine("I cannot handle more than twenty one digits!");
+ return;
+}
+
+BigInteger number = BigInteger.Parse(input);
+
+WriteLine($"{number:N0} in words is {number.ToWords()}.");
\ No newline at end of file
diff --git a/vscode/Chapter08/Ch08Ex03NumbersAsWordsLib/Ch08Ex03NumbersAsWordsLib.csproj b/vscode/Chapter08/Ch08Ex03NumbersAsWordsLib/Ch08Ex03NumbersAsWordsLib.csproj
new file mode 100644
index 0000000..9f5c4f4
--- /dev/null
+++ b/vscode/Chapter08/Ch08Ex03NumbersAsWordsLib/Ch08Ex03NumbersAsWordsLib.csproj
@@ -0,0 +1,7 @@
+
+
+
+ netstandard2.0
+
+
+
diff --git a/vscode/Chapter08/Ch08Ex03NumbersAsWordsLib/NumbersToWords.cs b/vscode/Chapter08/Ch08Ex03NumbersAsWordsLib/NumbersToWords.cs
new file mode 100644
index 0000000..5ea0834
--- /dev/null
+++ b/vscode/Chapter08/Ch08Ex03NumbersAsWordsLib/NumbersToWords.cs
@@ -0,0 +1,220 @@
+using System.Diagnostics; // Trace
+using System.IO; // File
+using System.Numerics; // BigInteger
+
+namespace Packt.Shared
+{
+ public static class NumbersToWords
+ {
+ // Single-digit and small number names
+ private static string[] smallNumbers = new string[]
+ {
+ "zero", "one", "two", "three", "four", "five", "six", "seven", "eight",
+ "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
+ "sixteen", "seventeen", "eighteen", "nineteen"
+ };
+
+ // Tens number names from twenty upwards
+ private static string[] tens = new string[]
+ {
+ "", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy",
+ "eighty", "ninety"
+ };
+
+ // Scale number names for use during recombination
+ private static string[] scaleNumbers = new string[]
+ {
+ "", "thousand", "million", "billion", "trillion",
+ "quadrillion", "quintillion"
+ };
+
+ private static int groups = 7; // i.e. up to quintillion
+
+ public static string ToWords(this int number)
+ {
+ return ToWords((BigInteger)number);
+ }
+
+ public static string ToWords(this long number)
+ {
+ return ToWords((BigInteger)number);
+ }
+
+ public static string ToWords(this BigInteger number)
+ {
+ /*
+ Convert A Number into Words
+ by Richard Carr, published at http://www.blackwasp.co.uk/numbertowords.aspx
+ */
+
+ /*
+ Zero Rule.
+ If the value is 0 then the number in words is 'zero' and no other rules apply.
+ */
+ if (number == 0)
+ {
+ return "zero";
+ }
+
+ /*
+ Three Digit Rule.
+ The integer value is split into groups of three digits starting from the
+ right-hand side. Each set of three digits is then processed individually
+ as a number of hundreds, tens and units. Once converted to text, the
+ three-digit groups are recombined with the addition of the relevant scale
+ number (thousand, million, billion).
+ */
+
+ // Array to hold the specified number of three-digit groups
+ int[] digitGroups = new int[groups];
+
+ // Ensure a positive number to extract from
+ var positive = BigInteger.Abs(number);
+
+ // Extract the three-digit groups
+ for (int i = 0; i < groups; i++)
+ {
+ digitGroups[i] = (int)(positive % 1000);
+ positive /= 1000;
+ }
+
+ // write to a text file in the project folder
+ Trace.Listeners.Add(new TextWriterTraceListener(
+ File.AppendText("log.txt")));
+
+ // text writer is buffered, so this option calls
+ // Flush() on all listeners after writing
+ Trace.AutoFlush = true;
+
+ // log array of group numbers
+ for (int x = 0; x < digitGroups.Length; x++)
+ {
+ Trace.WriteLine(string.Format(
+ format: "digitGroups[{0}] = {1}",
+ arg0: x,
+ arg1: digitGroups[x]));
+ }
+
+ // Convert each three-digit group to words
+ string[] groupTexts = new string[groups];
+
+ for (int i = 0; i < groups; i++)
+ {
+ // call a local function (see below)
+ groupTexts[i] = ThreeDigitGroupToWords(digitGroups[i]);
+ }
+
+ // log array of group texts
+ for (int x = 0; x < groupTexts.Length; x++)
+ {
+ Trace.WriteLine(string.Format(
+ format: "groupTexts[{0}] = {1}",
+ arg0: x,
+ arg1: groupTexts[x]));
+ }
+
+ /*
+ Recombination Rules.
+ When recombining the translated three-digit groups, each group except the
+ last is followed by a large number name and a comma, unless the group is
+ blank and therefore not included at all. One exception is when the final
+ group does not include any hundreds and there is more than one non-blank
+ group. In this case, the final comma is replaced with 'and'. eg.
+ 'one billion, one million and twelve'.
+ */
+
+ // Recombine the three-digit groups
+ string combined = groupTexts[0];
+ bool appendAnd;
+
+ // Determine whether an 'and' is needed
+ appendAnd = (digitGroups[0] > 0) && (digitGroups[0] < 100);
+
+ // Process the remaining groups in turn, smallest to largest
+ for (int i = 1; i < groups; i++)
+ {
+ // Only add non-zero items
+ if (digitGroups[i] != 0)
+ {
+ // Build the string to add as a prefix
+ string prefix = groupTexts[i] + " " + scaleNumbers[i];
+
+ if (combined.Length != 0)
+ {
+ prefix += appendAnd ? " and " : ", ";
+ }
+
+ // Opportunity to add 'and' is ended
+ appendAnd = false;
+
+ // Add the three-digit group to the combined string
+ combined = prefix + combined;
+ }
+ }
+
+ // Converts a three-digit group into English words
+ string ThreeDigitGroupToWords(int threeDigits)
+ {
+ // Initialise the return text
+ string groupText = "";
+
+ // Determine the hundreds and the remainder
+ int hundreds = threeDigits / 100;
+ int tensUnits = threeDigits % 100;
+
+ /*
+ Hundreds Rules.
+ If the hundreds portion of a three-digit group is not zero, the number of
+ hundreds is added as a word. If the three-digit group is exactly divisible
+ by one hundred, the text 'hundred' is appended. If not, the text
+ "hundred and" is appended. eg. 'two hundred' or 'one hundred and twelve'
+ */
+
+ if (hundreds != 0)
+ {
+ groupText += smallNumbers[hundreds] + " hundred";
+
+ if (tensUnits != 0)
+ {
+ groupText += " and ";
+ }
+ }
+
+ // Determine the tens and units
+ int tens = tensUnits / 10;
+ int units = tensUnits % 10;
+
+ /* Tens Rules.
+ If the tens section of a three-digit group is two or higher, the appropriate
+ '-ty' word (twenty, thirty, etc.) is added to the text and followed by the
+ name of the third digit (unless the third digit is a zero, which is ignored).
+ If the tens and the units are both zero, no text is added. For any other value,
+ the name of the one or two-digit number is added as a special case.
+ */
+
+ if (tens >= 2)
+ {
+ groupText += NumbersToWords.tens[tens];
+ if (units != 0)
+ {
+ groupText += " " + smallNumbers[units];
+ }
+ }
+ else if (tensUnits != 0)
+ groupText += smallNumbers[tensUnits];
+
+ return groupText;
+ }
+
+ /* Negative Rule.
+ Negative numbers are always preceded by the text 'negative'.
+ */
+ if (number < 0)
+ {
+ combined = "negative " + combined;
+ }
+
+ return combined;
+ }
+ }
+}
diff --git a/vscode/Chapter08/Ch08Ex03NumbersAsWordsTests/Ch08Ex03NumbersAsWordsTests.csproj b/vscode/Chapter08/Ch08Ex03NumbersAsWordsTests/Ch08Ex03NumbersAsWordsTests.csproj
new file mode 100644
index 0000000..de9c08a
--- /dev/null
+++ b/vscode/Chapter08/Ch08Ex03NumbersAsWordsTests/Ch08Ex03NumbersAsWordsTests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ net7.0
+ enable
+ enable
+
+ false
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/vscode/Chapter08/Ch08Ex03NumbersAsWordsTests/NumbersToWordsUnitTests.cs b/vscode/Chapter08/Ch08Ex03NumbersAsWordsTests/NumbersToWordsUnitTests.cs
new file mode 100644
index 0000000..9a386df
--- /dev/null
+++ b/vscode/Chapter08/Ch08Ex03NumbersAsWordsTests/NumbersToWordsUnitTests.cs
@@ -0,0 +1,64 @@
+using Packt.Shared; // ToWords extension method
+using System.Numerics; // BigInteger
+
+namespace Ch08Ex03NumbersAsWordsTests
+{
+ public class NumbersToWordsUnitTests
+ {
+ [Fact]
+ public void Test_Int32_0()
+ {
+ // arrange
+ int number = 0;
+ string expected = "zero";
+
+ // act
+ string actual = number.ToWords();
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void Test_Int32_1234()
+ {
+ // arrange
+ int number = 1234;
+ string expected = "one thousand, two hundred and thirty four";
+
+ // act
+ string actual = number.ToWords();
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void Test_BigInteger_18456002032011000007()
+ {
+ // arrange
+ BigInteger number = BigInteger.Parse("18456002032011000007");
+ string expected = "eighteen quintillion, four hundred and fifty six quadrillion, two trillion, thirty two billion, eleven million and seven";
+
+ // act
+ string actual = number.ToWords();
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
+ [Fact]
+ public void Test_Int32_minus_13()
+ {
+ // arrange
+ int number = -13;
+ string expected = "negative thirteen";
+
+ // act
+ string actual = number.ToWords();
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+ }
+}
\ No newline at end of file
diff --git a/vscode/Chapter08/Ch08Ex03NumbersAsWordsTests/Usings.cs b/vscode/Chapter08/Ch08Ex03NumbersAsWordsTests/Usings.cs
new file mode 100644
index 0000000..8c927eb
--- /dev/null
+++ b/vscode/Chapter08/Ch08Ex03NumbersAsWordsTests/Usings.cs
@@ -0,0 +1 @@
+global using Xunit;
\ No newline at end of file
diff --git a/vscode/Chapter08/Chapter08.code-workspace b/vscode/Chapter08/Chapter08.code-workspace
new file mode 100644
index 0000000..9156337
--- /dev/null
+++ b/vscode/Chapter08/Chapter08.code-workspace
@@ -0,0 +1,34 @@
+{
+ "folders": [
+ {
+ "path": "WorkingWithNumbers"
+ },
+ {
+ "path": "WorkingWithText"
+ },
+ {
+ "path": "WorkingWithRegularExpressions"
+ },
+ {
+ "path": "WorkingWithCollections"
+ },
+ {
+ "path": "WorkingWithRanges"
+ },
+ {
+ "path": "WorkingWithNetworkResources"
+ },
+ {
+ "path": "Ch08Ex02RegularExpressions"
+ },
+ {
+ "path": "Ch08Ex03NumbersAsWordsLib"
+ },
+ {
+ "path": "Ch08Ex03NumbersAsWordsApp"
+ },
+ {
+ "path": "Ch08Ex03NumbersAsWordsTests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/vscode/Chapter08/WorkingWithCollections/Program.Helpers.cs b/vscode/Chapter08/WorkingWithCollections/Program.Helpers.cs
new file mode 100644
index 0000000..a31041c
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithCollections/Program.Helpers.cs
@@ -0,0 +1,21 @@
+partial class Program
+{
+ static void Output(string title, IEnumerable collection)
+ {
+ WriteLine(title);
+ foreach (string item in collection)
+ {
+ WriteLine($" {item}");
+ }
+ }
+
+ static void OutputPQ(string title,
+ IEnumerable<(TElement Element, TPriority Priority)> collection)
+ {
+ WriteLine(title);
+ foreach ((TElement, TPriority) item in collection)
+ {
+ WriteLine($" {item.Item1}: {item.Item2}");
+ }
+ }
+}
diff --git a/vscode/Chapter08/WorkingWithCollections/Program.cs b/vscode/Chapter08/WorkingWithCollections/Program.cs
new file mode 100644
index 0000000..599e08e
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithCollections/Program.cs
@@ -0,0 +1,129 @@
+using System.Collections.Immutable;
+
+// Working with lists
+
+// Simple syntax for creating a list and adding three items
+List cities = new();
+cities.Add("London");
+cities.Add("Paris");
+cities.Add("Milan");
+
+/* Alternative syntax that is converted by the compiler into
+ the three Add method calls above
+List cities = new()
+ { "London", "Paris", "Milan" }; */
+
+/* Alternative syntax that passes an
+ array of string values to AddRange method
+List cities = new();
+cities.AddRange(new[] { "London", "Paris", "Milan" }); */
+
+Output("Initial list", cities);
+WriteLine($"The first city is {cities[0]}.");
+WriteLine($"The last city is {cities[cities.Count - 1]}.");
+
+cities.Insert(0, "Sydney");
+Output("After inserting Sydney at index 0", cities);
+
+cities.RemoveAt(1);
+cities.Remove("Milan");
+Output("After removing two cities", cities);
+
+// Working with dictionaries
+
+Dictionary keywords = new();
+
+// add using named parameters
+keywords.Add(key: "int", value: "32-bit integer data type");
+
+// add using positional parameters
+keywords.Add("long", "64-bit integer data type");
+keywords.Add("float", "Single precision floating point number");
+
+/* Alternative syntax; compiler converts this to calls to Add method
+Dictionary keywords = new()
+{
+ { "int", "32-bit integer data type" },
+ { "long", "64-bit integer data type" },
+ { "float", "Single precision floating point number" },
+}; */
+
+/* Alternative syntax; compiler converts this to calls to Add method
+Dictionary keywords = new()
+{
+ ["int"] = "32-bit integer data type",
+ ["long"] = "64-bit integer data type",
+ ["float"] = "Single precision floating point number", // last comma is optional
+}; */
+
+Output("Dictionary keys:", keywords.Keys);
+Output("Dictionary values:", keywords.Values);
+
+WriteLine("Keywords and their definitions");
+foreach (KeyValuePair item in keywords)
+{
+ WriteLine($" {item.Key}: {item.Value}");
+}
+
+// lookup a value using a key
+string key = "long";
+WriteLine($"The definition of {key} is {keywords[key]}");
+
+// Working with queues
+
+Queue coffee = new();
+
+coffee.Enqueue("Damir"); // front of queue
+coffee.Enqueue("Andrea");
+coffee.Enqueue("Ronald");
+coffee.Enqueue("Amin");
+coffee.Enqueue("Irina"); // back of queue
+
+Output("Initial queue from front to back", coffee);
+
+// server handles next person in queue
+string served = coffee.Dequeue();
+WriteLine($"Served: {served}.");
+
+// server handles next person in queue
+served = coffee.Dequeue();
+WriteLine($"Served: {served}.");
+Output("Current queue from front to back", coffee);
+
+WriteLine($"{coffee.Peek()} is next in line.");
+Output("Current queue from front to back", coffee);
+
+// Working with priority queues
+
+PriorityQueue vaccine = new();
+
+// add some people
+// 1 = high priority people in their 70s or poor health
+// 2 = medium priority e.g. middle aged
+// 3 = low priority e.g. teens and twenties
+
+vaccine.Enqueue("Pamela", 1); // my mum (70s)
+vaccine.Enqueue("Rebecca", 3); // my niece (teens)
+vaccine.Enqueue("Juliet", 2); // my sister (40s)
+vaccine.Enqueue("Ian", 1); // my dad (70s)
+
+OutputPQ("Current queue for vaccination:", vaccine.UnorderedItems);
+
+WriteLine($"{vaccine.Dequeue()} has been vaccinated.");
+WriteLine($"{vaccine.Dequeue()} has been vaccinated.");
+OutputPQ("Current queue for vaccination:", vaccine.UnorderedItems);
+
+WriteLine($"{vaccine.Dequeue()} has been vaccinated.");
+
+WriteLine("Adding Mark to queue with priority 2");
+vaccine.Enqueue("Mark", 2); // me (50s)
+
+WriteLine($"{vaccine.Peek()} will be next to be vaccinated.");
+OutputPQ("Current queue for vaccination:", vaccine.UnorderedItems);
+
+// Working with immutable collections
+
+ImmutableList immutableCities = cities.ToImmutableList();
+ImmutableList newList = immutableCities.Add("Rio");
+Output("Immutable list of cities:", immutableCities);
+Output("New list of cities:", newList);
diff --git a/vscode/Chapter08/WorkingWithCollections/WorkingWithCollections.csproj b/vscode/Chapter08/WorkingWithCollections/WorkingWithCollections.csproj
new file mode 100644
index 0000000..cd63b28
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithCollections/WorkingWithCollections.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vscode/Chapter08/WorkingWithNetworkResources/Program.cs b/vscode/Chapter08/WorkingWithNetworkResources/Program.cs
new file mode 100644
index 0000000..e614734
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithNetworkResources/Program.cs
@@ -0,0 +1,50 @@
+using System.Net; // IPHostEntry, Dns, IPAddress
+using System.Net.NetworkInformation; // Ping, PingReply, IPStatus
+
+// Working with URIs, DNS, and IP addresses
+
+Write("Enter a valid web address (or press Enter): ");
+string? url = ReadLine();
+
+if (string.IsNullOrWhiteSpace(url)) // if they enter nothing...
+{
+ // ... set a default URL
+ url = "https://stackoverflow.com/search?q=securestring";
+}
+
+Uri uri = new(url);
+WriteLine($"URL: {url}");
+WriteLine($"Scheme: {uri.Scheme}");
+WriteLine($"Port: {uri.Port}");
+WriteLine($"Host: {uri.Host}");
+WriteLine($"Path: {uri.AbsolutePath}");
+WriteLine($"Query: {uri.Query}");
+
+IPHostEntry entry = Dns.GetHostEntry(uri.Host);
+WriteLine($"{entry.HostName} has the following IP addresses:");
+foreach (IPAddress address in entry.AddressList)
+{
+ WriteLine($" {address} ({address.AddressFamily})");
+}
+
+// Pinging a server
+
+try
+{
+ Ping ping = new();
+
+ WriteLine("Pinging server. Please wait...");
+ PingReply reply = ping.Send(uri.Host);
+ WriteLine($"{uri.Host} was pinged and replied: {reply.Status}.");
+
+ if (reply.Status == IPStatus.Success)
+ {
+ WriteLine("Reply from {0} took {1:N0}ms",
+ arg0: reply.Address,
+ arg1: reply.RoundtripTime);
+ }
+}
+catch (Exception ex)
+{
+ WriteLine($"{ex.GetType().ToString()} says {ex.Message}");
+}
diff --git a/vscode/Chapter08/WorkingWithNetworkResources/WorkingWithNetworkResources.csproj b/vscode/Chapter08/WorkingWithNetworkResources/WorkingWithNetworkResources.csproj
new file mode 100644
index 0000000..91807b2
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithNetworkResources/WorkingWithNetworkResources.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vscode/Chapter08/WorkingWithNumbers/Program.cs b/vscode/Chapter08/WorkingWithNumbers/Program.cs
new file mode 100644
index 0000000..7985a1e
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithNumbers/Program.cs
@@ -0,0 +1,27 @@
+using System.Numerics;
+
+WriteLine("Working with large integers:");
+WriteLine("-----------------------------------");
+
+ulong big = ulong.MaxValue;
+WriteLine($"{big,40:N0}");
+
+BigInteger bigger =
+ BigInteger.Parse("123456789012345678901234567890");
+WriteLine($"{bigger,40:N0}");
+
+WriteLine("Working with complex numbers:");
+
+Complex c1 = new(real: 4, imaginary: 2);
+Complex c2 = new(real: 3, imaginary: 7);
+Complex c3 = c1 + c2;
+
+// output using default ToString implementation
+WriteLine($"{c1} added to {c2} is {c3}");
+
+// output using custom format
+WriteLine("{0} + {1}i added to {2} + {3}i is {4} + {5}i",
+ c1.Real, c1.Imaginary,
+ c2.Real, c2.Imaginary,
+ c3.Real, c3.Imaginary);
+
diff --git a/vscode/Chapter08/WorkingWithNumbers/WorkingWithNumbers.csproj b/vscode/Chapter08/WorkingWithNumbers/WorkingWithNumbers.csproj
new file mode 100644
index 0000000..cd63b28
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithNumbers/WorkingWithNumbers.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vscode/Chapter08/WorkingWithRanges/Program.cs b/vscode/Chapter08/WorkingWithRanges/Program.cs
new file mode 100644
index 0000000..584461f
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithRanges/Program.cs
@@ -0,0 +1,28 @@
+string name = "Samantha Jones";
+
+// getting the lengths of the first and last names
+
+int lengthOfFirst = name.IndexOf(' ');
+int lengthOfLast = name.Length - lengthOfFirst - 1;
+
+// Using Substring
+
+string firstName = name.Substring(
+ startIndex: 0,
+ length: lengthOfFirst);
+
+string lastName = name.Substring(
+ startIndex: name.Length - lengthOfLast,
+ length: lengthOfLast);
+
+WriteLine($"First name: {firstName}, Last name: {lastName}");
+
+// Using spans
+
+ReadOnlySpan nameAsSpan = name.AsSpan();
+ReadOnlySpan firstNameSpan = nameAsSpan[0..lengthOfFirst];
+ReadOnlySpan lastNameSpan = nameAsSpan[^lengthOfLast..^0];
+
+WriteLine("First name: {0}, Last name: {1}",
+ arg0: firstNameSpan.ToString(),
+ arg1: lastNameSpan.ToString());
diff --git a/vscode/Chapter08/WorkingWithRanges/WorkingWithRanges.csproj b/vscode/Chapter08/WorkingWithRanges/WorkingWithRanges.csproj
new file mode 100644
index 0000000..cd63b28
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithRanges/WorkingWithRanges.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vscode/Chapter08/WorkingWithRegularExpressions/Program.cs b/vscode/Chapter08/WorkingWithRegularExpressions/Program.cs
new file mode 100644
index 0000000..434bbd8
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithRegularExpressions/Program.cs
@@ -0,0 +1,38 @@
+using System.Text.RegularExpressions; // Regex
+
+Write("Enter your age: ");
+string input = ReadLine()!; // null-forgiving
+
+Regex ageChecker = new(@"^\d+$");
+
+if (ageChecker.IsMatch(input))
+{
+ WriteLine("Thank you!");
+}
+else
+{
+ WriteLine($"This is not a valid age: {input}");
+}
+
+string films = "\"Monsters, Inc.\",\"I, Tonya\",\"Lock, Stock and Two Smoking Barrels\"";
+
+WriteLine($"Films to split: {films}");
+
+string[] filmsDumb = films.Split(',');
+
+WriteLine("Splitting with string.Split method:");
+foreach (string film in filmsDumb)
+{
+ WriteLine(film);
+}
+
+Regex csv = new(
+ "(?:^|,)(?=[^\"]|(\")?)\"?((?(1)[^\"]*|[^,\"]*))\"?(?=,|$)");
+
+MatchCollection filmsSmart = csv.Matches(films);
+
+WriteLine("Splitting with regular expression:");
+foreach (Match film in filmsSmart)
+{
+ WriteLine(film.Groups[2].Value);
+}
diff --git a/vscode/Chapter08/WorkingWithRegularExpressions/WorkingWithRegularExpressions.csproj b/vscode/Chapter08/WorkingWithRegularExpressions/WorkingWithRegularExpressions.csproj
new file mode 100644
index 0000000..cd63b28
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithRegularExpressions/WorkingWithRegularExpressions.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/vscode/Chapter08/WorkingWithText/Program.cs b/vscode/Chapter08/WorkingWithText/Program.cs
new file mode 100644
index 0000000..139e2ed
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithText/Program.cs
@@ -0,0 +1,45 @@
+string city = "London";
+WriteLine($"{city} is {city.Length} characters long.");
+
+WriteLine($"First char is {city[0]} and third is {city[2]}.");
+
+string cities = "Paris,Tehran,Chennai,Sydney,New York,Medellín";
+
+string[] citiesArray = cities.Split(',');
+
+WriteLine($"There are {citiesArray.Length} items in the array.");
+
+foreach (string item in citiesArray)
+{
+ WriteLine(item);
+}
+
+string fullName = "Alan Jones";
+
+int indexOfTheSpace = fullName.IndexOf(' ');
+
+string firstName = fullName.Substring(
+ startIndex: 0, length: indexOfTheSpace);
+
+string lastName = fullName.Substring(
+ startIndex: indexOfTheSpace + 1);
+
+WriteLine($"Original: {fullName}");
+WriteLine($"Swapped: {lastName}, {firstName}");
+
+string company = "Microsoft";
+bool startsWithM = company.StartsWith("M");
+bool containsN = company.Contains("N");
+WriteLine($"Text: {company}");
+WriteLine($"Starts with M: {startsWithM}, contains an N: {containsN}");
+
+string recombined = string.Join(" => ", citiesArray);
+WriteLine(recombined);
+
+string fruit = "Apples";
+decimal price = 0.39M;
+DateTime when = DateTime.Today;
+
+WriteLine($"Interpolated: {fruit} cost {price:C} on {when:dddd}.");
+WriteLine(string.Format("string.Format: {0} cost {1:C} on {2:dddd}.",
+ arg0: fruit, arg1: price, arg2: when));
diff --git a/vscode/Chapter08/WorkingWithText/WorkingWithText.csproj b/vscode/Chapter08/WorkingWithText/WorkingWithText.csproj
new file mode 100644
index 0000000..cd63b28
--- /dev/null
+++ b/vscode/Chapter08/WorkingWithText/WorkingWithText.csproj
@@ -0,0 +1,14 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+
+
+
+
+
+
+