commit 3768d366ffc5e2123e05f4a15fe34a41dc3bb7d2 Author: github-classroom[bot] <66690702+github-classroom[bot]@users.noreply.github.com> Date: Wed Dec 11 15:23:18 2024 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9209049 --- /dev/null +++ b/.gitignore @@ -0,0 +1,583 @@ +# Created by https://www.toptal.com/developers/gitignore/api/csharp,visualstudio +# Edit at https://www.toptal.com/developers/gitignore?templates=csharp,visualstudio + +### Csharp ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +### VisualStudio ### + +# User-specific files + +# User-specific files (MonoDevelop/Xamarin Studio) + +# Mono auto generated files + +# Build results + +# Visual Studio 2015/2017 cache/options directory +# Uncomment if you have tasks that create the project's static files in wwwroot + +# Visual Studio 2017 auto generated files + +# MSTest test Results + +# NUnit + +# Build Results of an ATL Project + +# Benchmark Results + +# .NET Core + +# ASP.NET Scaffolding + +# StyleCop + +# Files built by Visual Studio + +# Chutzpah Test files + +# Visual C++ cache files + +# Visual Studio profiler + +# Visual Studio Trace Files + +# TFS 2012 Local Workspace + +# Guidance Automation Toolkit + +# ReSharper is a .NET coding add-in + +# TeamCity is a build add-in + +# DotCover is a Code Coverage Tool + +# AxoCover is a Code Coverage Tool + +# Coverlet is a free, cross platform Code Coverage Tool + +# Visual Studio code coverage results + +# NCrunch + +# MightyMoose + +# Web workbench (sass) + +# Installshield output folder + +# DocProject is a documentation generator add-in + +# Click-Once directory + +# Publish Web Output +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted + +# NuGet Packages +# NuGet Symbol Packages +# The packages folder can be ignored because of Package Restore +# except build/, which is used as an MSBuild target. +# Uncomment if necessary however generally it will be regenerated when needed +# NuGet v3's project.json files produces more ignorable files + +# Microsoft Azure Build Output + +# Microsoft Azure Emulator + +# Windows Store app package directories and files + +# Visual Studio cache files +# files ending in .cache can be ignored +# but keep track of directories ending in .cache + +# Others + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) + +# RIA/Silverlight projects + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) + +# SQL Server files + +# Business Intelligence projects + +# Microsoft Fakes + +# GhostDoc plugin setting file + +# Node.js Tools for Visual Studio + +# Visual Studio 6 build log + +# Visual Studio 6 workspace options file + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) + +# Visual Studio 6 technical files + +# Visual Studio LightSwitch build output + +# Paket dependency manager + +# FAKE - F# Make + +# CodeRush personal settings + +# Python Tools for Visual Studio (PTVS) + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio + +# Telerik's JustMock configuration file + +# BizTalk build output + +# OpenCover UI analysis results + +# Azure Stream Analytics local run output + +# MSBuild Binary and Structured Log + +# NVidia Nsight GPU debugger configuration file + +# MFractors (Xamarin productivity tool) working folder + +# Local History for Visual Studio + +# Visual Studio History (VSHistory) files + +# BeatPulse healthcheck temp database + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 + +# Ionide (cross platform F# VS Code tools) working folder + +# Fody - auto-generated XML schema + +# VS Code files for those working on multiple tools + +# Local History for Visual Studio Code + +# Windows Installer files from build outputs + +# JetBrains Rider + +### VisualStudio Patch ### +# Additional files built by Visual Studio + +# End of https://www.toptal.com/developers/gitignore/api/csharp,visualstudio \ No newline at end of file diff --git a/SantaClausInc.Test/GoodAndNaughtyListTests.cs b/SantaClausInc.Test/GoodAndNaughtyListTests.cs new file mode 100644 index 0000000..f961b97 --- /dev/null +++ b/SantaClausInc.Test/GoodAndNaughtyListTests.cs @@ -0,0 +1,72 @@ +using SantaClausInc.Core; + +namespace SantaClausInc.Test; + +public sealed class GoodAndNaughtyListTests +{ + [Theory] + [InlineData(-10, 0, "very naughty")] + [InlineData(0, 0, "boring child")] + [InlineData(1.4D, 2, "floored to next whole number")] + [InlineData(0.65D, 0, "not good enough to get a present")] + [InlineData(0.7D, 1, "good enough for a single present")] + [InlineData(5.4D, 8, "best child ever")] + public void CalcParcels(double goodOrNaughty, int expected, string reason) + { + GoodAndNaughtyList.CalcParcels(goodOrNaughty) + .Should().Be(expected, reason); + } + + [Fact] + public void GetAllCities_Simple() + { + var list = new GoodAndNaughtyList(SampleData.CreateMinimalSampleChildren()); + + var cities = list.GetAllCities(); + + cities.Count.Should().Be(2, "two cities"); + (cities[0], cities[1]).Should().Be(("Linz", "Leonding")); + } + + [Fact] + public void GetAllCities_DistinctResults() + { + var list = new GoodAndNaughtyList(SampleData.CreateSampleChildren()); + + var cities = list.GetAllCities(); + + cities.Count.Should().Be(3, "three distinct cities, skip duplicates"); + (cities[0], cities[1], cities[2]).Should().Be(("Linz", "Leonding", "Steyr")); + } + + [Fact] + public void GetAllCities_Empty() + { + var list = new GoodAndNaughtyList(new List()); + + var cities = list.GetAllCities(); + + cities.Count.Should().Be(0, "empty list of children"); + } + + [Fact] + public void GetChildrenByCity_Simple() + { + var list = new GoodAndNaughtyList(SampleData.CreateSampleChildren()); + + var children = list.GetChildrenByCity("Linz"); + + children.Count.Should().Be(5, "five kids live in Linz"); + } + + [Fact] + public void GetChildrenByCity_None() + { + var list = new GoodAndNaughtyList(SampleData.CreateSampleChildren()); + + var children = list.GetChildrenByCity("Hintertupfing"); + + children.Should().NotBeNull(); + children.Count.Should().Be(0, "five kids live in Linz"); + } +} diff --git a/SantaClausInc.Test/ListTests.cs b/SantaClausInc.Test/ListTests.cs new file mode 100644 index 0000000..6b2d71b --- /dev/null +++ b/SantaClausInc.Test/ListTests.cs @@ -0,0 +1,84 @@ +namespace SantaClausInc.Test; + +public sealed class ListTests +{ + [Fact] + public void Construction() + { + var list = new List(); + + list.Count.Should().Be(0, "no values added so far"); + list.Capacity.Should().Be(List.InitialCapacity, "capacity is >0 right away"); + } + + [Fact] + public void Add_Single() + { + const long Value = 44L; + var list = new List(); + + list.Add(Value); + + list.Count.Should().Be(1, "one item added"); + list.Capacity.Should().Be(List.InitialCapacity, "did not have to grow yet"); + list[0].Should() + .NotBe(default(long), "valid index") + .And.Be(Value, "value added to list is stored within and can be accessed"); + } + + [Fact] + public void Add_Multiple() + { + const string Value1 = "foo"; + const string? Value2 = null; + const string Value3 = "bar"; + var list = new List(); + + list.Add(Value1); + list.Add(Value2); + list.Add(Value3); + + list.Count.Should().Be(3, "three items added"); + list.Capacity.Should().Be(List.InitialCapacity, "did not have to grow yet"); + (list[0], list[1], list[2]).Should().Be((Value1, Value2, Value3), + "values added are stored in the order in which they have been added"); + } + + [Fact] + public void Add_TriggerGrowth() + { + const int Times = 100; + var startDate = DateOnly.FromDateTime(DateTime.Now); + var date = startDate; + var list = new List(); + + for (var i = 0; i < Times; i++) + { + list.Add(date); + date = date.AddDays(1); + } + + list.Count.Should().Be(Times); + list[Times - 1].Should().Be(startDate.AddDays(Times - 1)); + list.Capacity.Should().Be(108, "grows by 1.5 every time the capacity is exceeded"); + } + + [Fact] + public void Indexer_Simple() + { + const float Value = 12.34F; + var list = new List(); + list.Add(Value); + + list[0].Should().Be(Value, "accessing value with proper index"); + } + + [Fact] + public void Indexer_Invalid() + { + var list = new List(); + list.Add(12.34F); + + list[10].Should().Be(default(float), "invalid index used"); + } +} diff --git a/SantaClausInc.Test/ParcelTests.cs b/SantaClausInc.Test/ParcelTests.cs new file mode 100644 index 0000000..9c51931 --- /dev/null +++ b/SantaClausInc.Test/ParcelTests.cs @@ -0,0 +1,16 @@ +using SantaClausInc.Core; + +namespace SantaClausInc.Test; + +public sealed class ParcelTests +{ + [Fact] + public void StringRepresentation() + { + var parcel = new Parcel(new ChildInfo("Horst", 72, "Leonding", 12, 8.9D), + "Bar"); + + parcel.ToString() + .Should().Be("Bar for Horst (age 72)"); + } +} diff --git a/SantaClausInc.Test/PreparationTests.cs b/SantaClausInc.Test/PreparationTests.cs new file mode 100644 index 0000000..8081c63 --- /dev/null +++ b/SantaClausInc.Test/PreparationTests.cs @@ -0,0 +1,57 @@ +using SantaClausInc.Core; + +namespace SantaClausInc.Test; + +public sealed class PreparationTests +{ + public PreparationTests() + { + RandomProvider.Random = new Random(54321); + } + + [Fact] + public void PrepareTours() + { + var list = new GoodAndNaughtyList(SampleData.CreateMinimalSampleChildren(false)); + + var sleighs = Preparation.PrepareTours(list); + + sleighs.Count.Should().Be(2, "one sleigh for each city"); + CheckSleigh(sleighs[0], "Linz", [ + "#01: Travelling to city Linz", + " #02: Moving to house with ID 1", + " #03: Putting into chimney: Book for Sepp (age 6)", + " #04: Putting into chimney: Model Car for Sepp (age 6)", + "#05: Tour done, returning to Northpole HQ" + ], "No pile loaded for naughty child => only 1, not 2"); + CheckSleigh(sleighs[1], "Leonding", [ + "#01: Travelling to city Leonding", + " #02: Moving to house with ID 2", + " #03: Putting into chimney: Toy Blocks for Lisa (age 12)", + " #04: Putting into chimney: Book for Lisa (age 12)", + " #05: Putting into chimney: Doll for Lisa (age 12)", + " #06: Putting into chimney: Watercolors for Lisa (age 12)", + " #07: Putting into chimney: Model Car for Lisa (age 12)", + "#08: Tour done, returning to Northpole HQ" + ]); + } + + private static void CheckSleigh(Sleigh? sleigh, string expectedCity, + string[] expectedTourPlan, string pileReason = "") + { + if (sleigh is null) + { + Assert.Fail("Sleigh is null"); + } + + sleigh.TargetCity.Should().Be(expectedCity); + + var tourPlan = sleigh.ExecuteTour(); + tourPlan.Count.Should().Be(expectedTourPlan.Length, pileReason); + + for (var i = 0; i < tourPlan.Count; i++) + { + tourPlan[i].Should().Be(expectedTourPlan[i]); + } + } +} diff --git a/SantaClausInc.Test/SampleData.cs b/SantaClausInc.Test/SampleData.cs new file mode 100644 index 0000000..f240b36 --- /dev/null +++ b/SantaClausInc.Test/SampleData.cs @@ -0,0 +1,36 @@ +using SantaClausInc.Core; + +namespace SantaClausInc.Test; + +internal static class SampleData +{ + public static List CreateSampleChildren() + { + var list = new List(); + list.Add(new ChildInfo("Sepp", 6, "Linz", 1, 1.4D)); + list.Add(new ChildInfo("Lisa", 12, "Leonding", 2, 3.8D)); + list.Add(new ChildInfo("Horst", 8, "Steyr", 3, 0.4D)); + list.Add(new ChildInfo("Lukas", 19, "Linz", 4, -4.3D)); + list.Add(new ChildInfo("Maria", 16, "Linz", 5, 2.5D)); + list.Add(new ChildInfo("Sebastian", 3, "Steyr", 6, 0.9D)); + list.Add(new ChildInfo("Regina", 9, "Linz", 7, -0.1D)); + list.Add(new ChildInfo("Hanna", 15, "Leonding", 8, 0.9D)); + list.Add(new ChildInfo("Emily", 4, "Linz", 9, 1.7D)); + list.Add(new ChildInfo("Martin", 11, "Leonding", 10, 3.1D)); + + return list; + } + + public static List CreateMinimalSampleChildren(bool setOne = true) + { + var list = new List(); + list.Add(new ChildInfo("Sepp", 6, "Linz", 1, 1.4D)); + list.Add(new ChildInfo("Lisa", 12, "Leonding", 2, 3.8D)); + if (!setOne) + { + list.Add(new ChildInfo("Hugo", 8, "Linz", 1, -0.3D)); + } + + return list; + } +} diff --git a/SantaClausInc.Test/SantaClausInc.Test.csproj b/SantaClausInc.Test/SantaClausInc.Test.csproj new file mode 100644 index 0000000..9117c16 --- /dev/null +++ b/SantaClausInc.Test/SantaClausInc.Test.csproj @@ -0,0 +1,37 @@ + + + + net9.0 + enable + enable + true + false + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + \ No newline at end of file diff --git a/SantaClausInc.Test/SleighTests.cs b/SantaClausInc.Test/SleighTests.cs new file mode 100644 index 0000000..72fb2a3 --- /dev/null +++ b/SantaClausInc.Test/SleighTests.cs @@ -0,0 +1,108 @@ +using SantaClausInc.Core; + +namespace SantaClausInc.Test; + +public sealed class SleighTests +{ + [Fact] + public void Construction() + { + const string City = "Salzburg"; + var sleigh = new Sleigh(5, City); + + sleigh.TargetCity.Should().Be(City); + } + + [Fact] + public void Load_Single() + { + var sleigh = new Sleigh(5, "Graz"); + + sleigh.Load(CreateParcelPile(true)) + .Should().BeTrue("enough capacity, can load this pile"); + } + + [Fact] + public void Load_Multiple() + { + var sleigh = new Sleigh(4, "Graz"); + + for (var i = 0; i < 4; i++) + { + sleigh.Load(CreateParcelPile(i % 2 == 0)) + .Should().BeTrue("still enough capacity"); + } + + sleigh.Load(CreateParcelPile(true)) + .Should().BeFalse("capacity exceeded"); + } + + [Fact] + public void ExecuteTour() + { + var sleigh = new Sleigh(5, "Bregenz"); + + sleigh.Load(CreateParcelPile(true)); + sleigh.Load(CreateParcelPile(false)); + + var tourPlan = sleigh.ExecuteTour(); + + tourPlan.Should().NotBeNull(); + tourPlan.Count.Should().Be(10); + for (var i = 0; i < tourPlan.Count; i++) + { + var (expected, reason) = i switch + { + 0 => ("#01: Travelling to city Bregenz", + "first sleigh has to travel to destination city"), + 1 => (" #02: Moving to house with ID 10", + "unloading starts at back of the sleight, last in, first out"), + 2 => (" #03: Putting into chimney: Doll for Martin (age 11)", + "topmost parcel goes in first"), + 3 => (" #04: Putting into chimney: Lego for Martin (age 11)", + "then the next parcel - mind the indents"), + 4 => (" #05: Moving to house with ID 5", + "done with this house, moving to the next"), + 5 => (" #06: Putting into chimney: Raspberry for Maria (age 16)", + "starting to process last pile on sleigh"), + 6 => (" #07: Putting into chimney: Doll for Maria (age 16)", + "processing next parcel"), + 7 => (" #08: Putting into chimney: Board Game for Maria (age 16)", + "processing next parcel"), + 8 => (" #09: Putting into chimney: Car for Maria (age 16)", + "last parcel in pile and the last pile"), + 9 => ("#10: Tour done, returning to Northpole HQ", "completed"), + _ => (null, "shouldn't exist") + }; + tourPlan[i].Should().Be(expected, reason); + } + } + + private static Stack CreateParcelPile(bool setOne) + { + var children = SampleData.CreateSampleChildren(); + var pile = new Stack(); + + var childId = setOne ? 4 : 9; + var child = children[childId]; + if (child is null) + { + Assert.Fail($"Child with id {childId} not found"); + } + + if (setOne) + { + pile.Push(new Parcel(child, "Car")); + pile.Push(new Parcel(child, "Board Game")); + pile.Push(new Parcel(child, "Doll")); + pile.Push(new Parcel(child, "Raspberry")); + } + else + { + pile.Push(new Parcel(child, "Lego")); + pile.Push(new Parcel(child, "Doll")); + } + + return pile; + } +} diff --git a/SantaClausInc.Test/StackTests.cs b/SantaClausInc.Test/StackTests.cs new file mode 100644 index 0000000..3980a40 --- /dev/null +++ b/SantaClausInc.Test/StackTests.cs @@ -0,0 +1,127 @@ +namespace SantaClausInc.Test; + +public sealed class StackTests +{ + [Fact] + public void Construction() + { + var stack = new Stack(); + + stack.Count.Should().Be(0); + } + + [Fact] + public void PushPop_Single() + { + const int Value = 6; + var stack = new Stack(); + + stack.Push(Value); + + stack.Count.Should().Be(1, "one item added"); + stack.Pop().Should().Be(Value, "we get back what we put in"); + stack.Count.Should().Be(0, "no items remain"); + } + + [Fact] + public void PushPop_Single_DifferentType() + { + const bool Value = true; + var stack = new Stack(); + + stack.Push(Value); + + stack.Count.Should().Be(1); + stack.Pop().Should().Be(Value, "we get back what we put in"); + ; + stack.Count.Should().Be(0, "no items remain"); + } + + [Fact] + public void PushPop_Nullable() + { + var stack = new Stack(); + + stack.Push(null); + + stack.Count.Should().Be(1, "we can push null as well"); + stack.Pop().Should().Be(null, "we get back what we put in, even if it was null"); + stack.Count.Should().Be(0, "no items remain"); + } + + [Fact] + public void PushPop_Multiple() + { + const string Value1 = "foo"; + const string Value2 = "bar"; + var stack = new Stack(); + + stack.Push(Value1); + stack.Push(Value2); + + stack.Count.Should().Be(2, "two items added"); + stack.Pop().Should().Be(Value2, "last in, first out"); + stack.Count.Should().Be(1, "one item remains"); + stack.Pop().Should().Be(Value1); + stack.Count.Should().Be(0, "all items removed"); + } + + [Fact] + public void Pop_Empty() + { + var stack1 = new Stack(); + var stack2 = new Stack(); + var stack3 = new Stack(); + + stack1.Pop().Should() + .Be(default(long), "popping from an empty stack returns the types default") + .And.Be(0L, "the default of long"); + stack2.Pop().Should() + .Be(default(object), "the default is dependent on the type parameter") + .And.Be(null, "the default of a reference type"); + stack3.Pop().Should() + .Be(null, "a little more complex for nullable value types") + .And.Be(null, "for which the default is null") + .And.NotBe(default(long), "but cannot be inferred correctly all the time"); + } + + [Fact] + public void Peek_Simple() + { + const char Value = 'H'; + var stack = new Stack(); + stack.Push(Value); + + stack.Count.Should().Be(1); + stack.Peek().Should().Be(Value, "we peek the top of the stack"); + stack.Count.Should().Be(1, "peek does not remove element from the stack"); + } + + [Fact] + public void Peek_Multiple() + { + const bool Value = true; + var stack = new Stack(); + stack.Push(Value); + stack.Push(null); + + stack.Count.Should().Be(2); + stack.Peek().Should().Be(null, "we peek the top of the stack"); + stack.Peek().Should().Be(null, "still the same element on top"); + stack.Pop().Should().Be(null); + stack.Peek().Should().Be(Value, "now we peek the value added first"); + stack.Count.Should().Be(1, "peek does not remove elements from the stack, but pop does"); + } + + [Fact] + public void Peek_Empty() + { + var stack = new Stack(); + + stack.Peek().Should() + .Be(default(object), "peeking an empty stack returns the type parameters default value"); + } + + // ReSharper disable once ClassNeverInstantiated.Local - used as type parameter in tests + private sealed record Foo; +} diff --git a/SantaClausInc.sln b/SantaClausInc.sln new file mode 100644 index 0000000..52cb384 --- /dev/null +++ b/SantaClausInc.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33103.184 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SantaClausInc", "SantaClausInc\SantaClausInc.csproj", "{7F30E637-BAFE-42FA-A173-F42B3902ED3B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SantaClausInc.Test", "SantaClausInc.Test\SantaClausInc.Test.csproj", "{161D9B2A-4E8B-43B6-A77E-40BED559521F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7F30E637-BAFE-42FA-A173-F42B3902ED3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F30E637-BAFE-42FA-A173-F42B3902ED3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F30E637-BAFE-42FA-A173-F42B3902ED3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F30E637-BAFE-42FA-A173-F42B3902ED3B}.Release|Any CPU.Build.0 = Release|Any CPU + {161D9B2A-4E8B-43B6-A77E-40BED559521F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {161D9B2A-4E8B-43B6-A77E-40BED559521F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {161D9B2A-4E8B-43B6-A77E-40BED559521F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {161D9B2A-4E8B-43B6-A77E-40BED559521F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D322E690-379C-4802-8F7E-1AC6AD090D59} + EndGlobalSection +EndGlobal diff --git a/SantaClausInc/Collections/List.cs b/SantaClausInc/Collections/List.cs new file mode 100644 index 0000000..a7de2ce --- /dev/null +++ b/SantaClausInc/Collections/List.cs @@ -0,0 +1,51 @@ +namespace SantaClausInc.Collections; + +/// +/// Represents a simple list, backed by an array, which can be used with a generic type +/// +/// Type of the values stored in the list +public sealed class List +{ + /// + /// The initial capacity with which the list is created + /// + public const int InitialCapacity = 4; + + // TODO field + + /// + /// Creates a new instance of the with the capacity + /// + public List() + { + // TODO + } + + /// + /// Gets the number of items currently stored in the list + /// + // TODO + + /// + /// Allows to access and item in the list by its index. + /// If an invalid index is supplied the default value for the type is returned. + /// + /// Index of the element to access; zero based + // TODO + + /// + /// Gets the current capacity of the list + /// + // TODO + + /// + /// Adds a new item to the list. Automatically increases capacity of the list if required. + /// Items are stored in the order in which they are added. + /// Duplicates are allowed. + /// + /// Value to add to the list + public void Add(TValue value) + { + // TODO + } +} diff --git a/SantaClausInc/Collections/Stack.cs b/SantaClausInc/Collections/Stack.cs new file mode 100644 index 0000000..724c8c7 --- /dev/null +++ b/SantaClausInc/Collections/Stack.cs @@ -0,0 +1,52 @@ +using System.Diagnostics.CodeAnalysis; + +namespace SantaClausInc.Collections; + +/// +/// Represents a stack which can be used with a generic type +/// +/// Type of the values stored in the stack +public sealed class Stack +{ + // TODO _head field + + /// + /// Creates a new instance of the + /// + public Stack() + { + // TODO + } + + /// + /// Gets the number of items on the stack + /// + // TODO + + // uncomment this once the fields are implemented to avoid redundant nullability warnings + //[MemberNotNullWhen(false, nameof(_head))] + private bool IsEmpty => false; // TODO actual implementation + + /// + /// Adds an item on top of the stack + /// + /// Value to add + public void Push(TValue value) + { + // TODO + } + + /// + /// Gets the top element on the stack without removing it. + /// If the stack is empty the default value for the type is returned. + /// + /// Top element of the stack or default + // TODO + + /// + /// Gets and removes the top element of the stack. + /// If the stack is empty the default value for the type is returned. + /// + /// Top element of the stack or default + // TODO +} diff --git a/SantaClausInc/Core/GoodAndNaughtyList.cs b/SantaClausInc/Core/GoodAndNaughtyList.cs new file mode 100644 index 0000000..a0d6458 --- /dev/null +++ b/SantaClausInc/Core/GoodAndNaughtyList.cs @@ -0,0 +1,49 @@ +namespace SantaClausInc.Core; + +/// +/// Represents the big list of all children and their good or naughty rating as kept by Santa +/// +public sealed class GoodAndNaughtyList +{ + private const double OneParcelFactor = 1.5D; + // TODO + + /// + /// Creates a new instance of the for the supplied children + /// + /// List of children to be managed in this good or naughty ledger + public GoodAndNaughtyList(List allChildren) + { + // TODO + } + + /// + /// Gets a list of children living in the specified city + /// + /// City to filter by + /// A list of children living in the specified city; can be empty + public List GetChildrenByCity(string city) + { + // TODO + return null!; + } + + /// + /// Gets a list of all cities children live in. + /// This list contains each city name only once and only contains cities in which at least one child lives. + /// + /// A distinct list of city names of cities in which children are living + public List GetAllCities() + { + // TODO + return null!; + } + + /// + /// Calculates the amount of presents a child will receive based on the good or naughty value. + /// The final amount is rounded down to the next whole number. + /// + /// Good or naughty value + /// The amount of presents + public static int CalcParcels(double goodOrNaughty) => -1; // TODO +} diff --git a/SantaClausInc/Core/Model.cs b/SantaClausInc/Core/Model.cs new file mode 100644 index 0000000..d8856b2 --- /dev/null +++ b/SantaClausInc/Core/Model.cs @@ -0,0 +1,26 @@ +namespace SantaClausInc.Core; + +/// +/// Represents information about a child as managed by Santa +/// +/// Name of the child +/// Age of the child +/// City in which the child lives +/// Citywide unique ID of the house the child lives in +/// Good or naughty factor of the child +public record ChildInfo(string Name, int Age, string City, int HouseId, double GoodOrNaughty); + +/// +/// Represents a present for a child +/// +/// Child who will receive the present +/// Description of the contained toy +public record Parcel(ChildInfo ForChild, string Description) +{ + /// + /// Creates a string representation of this containing information about + /// the child for which it is meant and the content + /// + /// A string representation of the present + public override string ToString() => string.Empty; // TODO +} diff --git a/SantaClausInc/Core/Preparation.cs b/SantaClausInc/Core/Preparation.cs new file mode 100644 index 0000000..8897df5 --- /dev/null +++ b/SantaClausInc/Core/Preparation.cs @@ -0,0 +1,27 @@ +namespace SantaClausInc.Core; + +/// +/// Utility class containing logic for preparing sleighs for the delivery tours. +/// +public static class Preparation +{ + private static readonly string[] giftTypes = + [ + "Stuffed Animal", "Model Rocket", "Doll", "Model Car", "Book", "Game", "Toy Blocks", "Watercolors" + ]; + + /// + /// Prepares the parcel piles and loads them on sleighs for the delivery tours at Christmas. + /// Each sleigh has a single city as target. + /// + /// + /// The good and naughty ledger used to determine the + /// amount of presents for each child + /// + /// List of sleighs loaded for the delivery tours + public static List PrepareTours(GoodAndNaughtyList goodAndNaughty) + { + // TODO + return null!; + } +} diff --git a/SantaClausInc/Core/RandomProvider.cs b/SantaClausInc/Core/RandomProvider.cs new file mode 100644 index 0000000..d5df68c --- /dev/null +++ b/SantaClausInc/Core/RandomProvider.cs @@ -0,0 +1,19 @@ +namespace SantaClausInc.Core; + +/// +/// Provides a instance. +/// +public static class RandomProvider +{ + private static Random? _overriddenInstance; + + /// + /// Gets a default or, if overridden, a custom random instance. + /// Allows to override the default random instance with a specific, seeded one. + /// + public static Random Random + { + get => _overriddenInstance ?? Random.Shared; + set => _overriddenInstance = value; + } +} diff --git a/SantaClausInc/Core/Sleigh.cs b/SantaClausInc/Core/Sleigh.cs new file mode 100644 index 0000000..8b78805 --- /dev/null +++ b/SantaClausInc/Core/Sleigh.cs @@ -0,0 +1,48 @@ +namespace SantaClausInc.Core; + +/// +/// Represents a reindeer powered sleigh operated by a Santa clone. +/// Contains the piles of presents to be distributed to the good children of a city. +/// +public sealed class Sleigh +{ + // TODO + + /// + /// Creates a new instance with the given capacity and target city + /// + /// Max. amount of parcel piles that can be loaded on this sleigh + /// City this sleigh will go to during the delivery tour + public Sleigh(int capacity, string targetCity) + { + // TODO + } + + /// + /// Gets the target city of this sleigh + /// + // TODO + + /// + /// Loads a pile of on the sleigh. + /// Piles loaded first can only be retrieved once those loaded last have been unloaded. + /// + /// Pile of presents to add + /// True if the pile could be loaded; false if there is no more room available + public bool Load(Stack parcelPile) + { + // TODO + return false; + } + + /// + /// Executes the delivery tour and creates a log of each step while doing so. + /// Presents are unloaded at the proper houses and the sleigh returns back to base once done. + /// + /// List of chronological log entries of the delivery tour + public List ExecuteTour() + { + // TODO + return null!; + } +} diff --git a/SantaClausInc/Program.cs b/SantaClausInc/Program.cs new file mode 100644 index 0000000..5efd4d9 --- /dev/null +++ b/SantaClausInc/Program.cs @@ -0,0 +1,49 @@ +using System.Text; +using SantaClausInc.Core; + +Console.OutputEncoding = Encoding.UTF8; + +Console.WriteLine("*** SantaClausInc ***"); + +var list = CreateList(); +var sleighs = Preparation.PrepareTours(list); + +for (var i = 0; i < sleighs.Count; i++) +{ + var sleigh = sleighs[i]; + var tourLog = sleigh!.ExecuteTour(); + for (var j = 0; j < tourLog.Count; j++) + { + Console.WriteLine(tourLog[j]); + } + + Console.WriteLine(); +} + +# region data creation + +static GoodAndNaughtyList CreateList() +{ + var children = CreateChildren(); + + return new GoodAndNaughtyList(children); +} + +static List CreateChildren() +{ + var list = new List(); + list.Add(new ChildInfo("Sepp", 6, "Linz", 1, 1.4D)); + list.Add(new ChildInfo("Lisa", 12, "Leonding", 2, 3.8D)); + list.Add(new ChildInfo("Horst", 8, "Steyr", 3, 0.4D)); + list.Add(new ChildInfo("Lukas", 19, "Linz", 4, -4.3D)); + list.Add(new ChildInfo("Maria", 16, "Linz", 5, 2.5D)); + list.Add(new ChildInfo("Sebastian", 3, "Steyr", 6, 0.9D)); + list.Add(new ChildInfo("Regina", 9, "Linz", 7, -0.1D)); + list.Add(new ChildInfo("Hanna", 15, "Leonding", 8, 0.9D)); + list.Add(new ChildInfo("Emily", 4, "Linz", 9, 1.7D)); + list.Add(new ChildInfo("Martin", 11, "Leonding", 10, 3.1D)); + + return list; +} + +# endregion diff --git a/SantaClausInc/SantaClausInc.csproj b/SantaClausInc/SantaClausInc.csproj new file mode 100644 index 0000000..e07206c --- /dev/null +++ b/SantaClausInc/SantaClausInc.csproj @@ -0,0 +1,20 @@ + + + + Exe + net9.0 + enable + enable + true + + + + + + + + + + + + \ No newline at end of file diff --git a/pics/sample_run.png b/pics/sample_run.png new file mode 100644 index 0000000..8c1a523 Binary files /dev/null and b/pics/sample_run.png differ diff --git a/pics/santa.png b/pics/santa.png new file mode 100644 index 0000000..6670f0c Binary files /dev/null and b/pics/santa.png differ diff --git a/readme.adoc b/readme.adoc new file mode 100644 index 0000000..d4b39ef --- /dev/null +++ b/readme.adoc @@ -0,0 +1,193 @@ +:sectnums: +:nofooter: +:toc: left +:icons: font +:data-uri: +:source-highlighter: highlightjs +:stem: latexmath + += Col.03 -- Santa Claus Inc. + +The Santa Claus Inc. company is preparing a very extensive delivery tour for Christmas. +Your task is to implement a tour planning software which will: + +* Determine how many and which presents each child will receive +* Load & prepare the sleighs +** Due to a recent efficiency program several Santa clones are available to operate multiple sleighs +* Send the sleighs on delivery tours to the various cities +* Prints the tour logs of the sleighs + +image::pics/santa.png[Santa's Sleigh] + +== Implementation + +.Logic +[plantuml] +---- +@startuml +hide empty fields +hide empty methods + +class GoodAndNaughtyList { + +GoodAndNaughtyList(List) + +List GetChildrenByCity(string) + +List GetAllCities() + {static} +int CalcParcels(double) +} +class ChildInfo { + +string Name [readonly] + +int Age [readonly] + +string City [readonly] + +int HouseId [readonly] + +double GoodOrNaughty [readonly] +} +class Parcel { + +ChildInfo ForChild [readonly] + +string Description [readonly] + + +string ToString() [override] +} +class Sleigh { + +string TargetCity [readonly] + + +Sleigh(int, string) + +bool Load(Stack) + +List ExecuteTour() +} + +Parcel "n" -l- "1" ChildInfo +Sleigh "1" -d- "n" Parcel +GoodAndNaughtyList "1" -- "n" ChildInfo + +@enduml +---- + +.Utility +[plantuml] +---- +@startuml +hide empty fields +hide empty methods + +class List { + +int InitialCapacity [const] + +int Count [private set] + +int Capacity [readonly] + +TValue? this[int index] + + +List() + +void Add(TValue) +} +class Stack { + +int Count [private set] + + +Stack() + +void Push(TValue) + +TValue? Peek() + +TValue? Pop() +} +class Preparation { + {static} +List PrepareTours(GoodAndNaughtyList) +} +class RandomProvider { + {static} +Random Random +} + +@enduml +---- + +NOTE: As usual extensive XMLDoc & Unit Tests have been provided + +=== Collections + +* To support the complex logic this application requires, supporting collections need to be _implemented_ +* These collections have to be implemented in a _generic_ fashion + +==== List + +* Implement as an _array list_ +* It has to be _generic_, accepting a `TValue` _type parameter_ +* The initial size is `4` +* The growth factor is `1.5` +** If the calculated size is not a whole number round _up_ to the next whole number +* Provides an _indexer_ +** If an invalid index is supplied the `default` value of the `TValue` type parameter is returned + +TIP: You do _not_ have to implement additional methods like `Remove` or `Contains` + +==== Stack + +* Implement a stack based on _nodes_ and _pointers_ +** You could add a _private_ node class +* It has to be generic, accepting a `TValue` type parameter +* Implement _correctly_: +** `Push` +** `Peek` +** `Pop` +* If the stack is empty `Peek` & `Pop` return the `default` value of the `TValue` type + +=== Tour Planning Logic + +* Read the provided XMLDoc carefully +* The following contains some additional explanations & clarifications for some parts of the implementation + +CAUTION: Just in case it is not clear enough for someone: you must _not_ use framework collections, but only those you implemented yourself in this assignment + +==== `GoodAndNaughtyList` + +* The 'good or naughty' rating of a child is multiplied by `1.5` to determine the amount of presents which will be allocated for that child +** A _negative_ (very naughty) rating does _not_ lead to the child having to give Santa presents 😉 +*** => the min. amount is `0` +** The amount of presents is rounded _down_ to the next whole number +** Only 'whole' presents are allocated -- no '0.6 presents' or something like that +* `GetAllCities` returns a _distinct_ list of city names + +==== `Parcel` + +* The `ToString` implementation follows the format ` for (age )` +** Example: `Watercolors for Josef (age 9)` + +==== `Preparation` + +* There is a defined set of possible toys which can be used in presents +* For each present one of the toys is selected by random +** Use `RandomProvider` for the selection logic +* For each city a single sleigh is prepared +** Make sure to set the capacity of the sleigh big enough to hold all parcel piles for all children who will receive at least one present in this city -- sleighs use advanced magic technology, so the size can be easily set as required +* For each child a dedicated pile of presents is prepared +** That pile has a minimal size of one, but can contain many more presents +** Each child has exactly one pile of presents +** No pile of presents contains presents of different children +** The amount of presents is determined by the 'good or naughty' value of each child +* Those piles loaded first on the sleigh will be delivered last +** Those loaded last will be removed first + +TIP: A sleigh is a _stack of stacks_ + +==== `RandomProvider` + +You don't have to implement anything here. +But you should read up on https://learn.microsoft.com/en-us/dotnet/api/system.random.shared?view=net-9.0[`Random.Shared`]. + +==== `Sleigh` + +* Several aspects of the sleighs have already been explained in previous sections +* When the sleigh executes its tour delivering all the presents to the deserving, good children it creates a log entry for each of the steps +** First it travels to a city +** Then to the house of the first child +** Then it delivers all the presents +** Then moves to the next house +** ... +** Once all presents have been delivered it returns home to the north-pole +* Each step gets assigned a unique, incrementing step number +* Be careful with the _indentation_ of the various log messages +** 'Inner' steps are indented more than 'outer' steps +** See the sample run output for an example +* The log entries are kept in a `List` + +== Program & Sample Run + +A `Program` file has been provided. +Executing it after implementing all required pieces should result in an output similar to the following -- toy selection can vary due to randomized generation. + +image::pics/sample_run.png[Sample Run] \ No newline at end of file