Initial commit

This commit is contained in:
github-classroom[bot] 2025-01-02 16:52:58 +00:00 committed by GitHub
commit eab553c714
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 5080 additions and 0 deletions

3553
.editorconfig Normal file

File diff suppressed because it is too large Load diff

583
.gitignore vendored Normal file
View file

@ -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

View file

@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<Using Include="FluentAssertions" />
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="7.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BuildingDirectory\BuildingDirectory.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,101 @@
using BuildingDirectory.Model;
namespace BuildingDirectory.Test;
public sealed class BuildingInformationTests
{
private static readonly string[] companyNames = ["Foo Inc.", "Bar GmbH", "Baz AG"];
private static readonly (BusinessCard Card, int Room)[] employees =
[
(new BusinessCard("A", "B", "Sales", null, "b@x.com"), 301),
(new BusinessCard("A", "C", "Development", null, "c@x.com"), 444),
(new BusinessCard("A", "D", "HR", null, "d@x.com"), 2),
(new BusinessCard("A", "E", "Maintenance", null, "e@x.com"), 102)
];
[Fact]
public void GetFloorSelection()
{
var info = CreateSampleInfo();
info.GetFloorSelection()
.Should().NotBeNullOrEmpty()
.And.HaveCount(2, "two floors defined")
.And.Contain([Floor.Ground, Floor.First]);
}
[Fact]
public void GetCompaniesOnFloor()
{
var info = CreateSampleInfo();
info.GetCompaniesOnFloor(Floor.Ground)
.Should().NotBeNullOrEmpty()
.And.HaveCount(1, "only one company on this floor")
.And.Contain([companyNames[1]]);
info.GetCompaniesOnFloor(Floor.First)
.Should().NotBeNullOrEmpty()
.And.HaveCount(2, "two companies on this floor")
.And.Contain([companyNames[0], companyNames[2]]);
info.GetCompaniesOnFloor(Floor.Second)
.Should().BeNull("no companies on this floor");
}
[Fact]
public void GetCompany_Exists()
{
var info = CreateSampleInfo();
var company1 = info.GetCompany(Floor.First, companyNames[0]);
company1.Should().NotBeNull();
company1!.Name.Should().Be(companyNames[0]);
company1.NoOfEmployees.Should().Be(1);
var company2 = info.GetCompany(Floor.Ground, companyNames[1]);
company2.Should().NotBeNull();
company2!.Name.Should().Be(companyNames[1]);
company2.NoOfEmployees.Should().Be(2);
var company3 = info.GetCompany(Floor.First, companyNames[2]);
company3.Should().NotBeNull();
company3!.Name.Should().Be(companyNames[2]);
company3.NoOfEmployees.Should().Be(1);
}
[Fact]
public void GetCompany_NotExists()
{
var info = CreateSampleInfo();
info.GetCompany(Floor.Ground, companyNames[0])
.Should().BeNull("company does not reside at this floor");
info.GetCompany(Floor.Second, companyNames[2])
.Should().BeNull("no companies on this floor");
}
private static BuildingInformation CreateSampleInfo()
{
var companiesGround = new MyDictionary<string, Company>();
var companiesFirst = new MyDictionary<string, Company>();
var employees1 = new MyDictionary<BusinessCard, int>();
var employees2 = new MyDictionary<BusinessCard, int>();
var employees3 = new MyDictionary<BusinessCard, int>();
AddEmployee(employees1, 0);
AddEmployee(employees2, 1);
AddEmployee(employees2, 2);
AddEmployee(employees3, 3);
companiesFirst.Add(companyNames[0], new Company(employees1, companyNames[0]));
companiesGround.Add(companyNames[1], new Company(employees2, companyNames[1]));
companiesFirst.Add(companyNames[2], new Company(employees3, companyNames[2]));
var floors = new MyDictionary<Floor, MyDictionary<string, Company>>();
floors.Add(Floor.Ground, companiesGround);
floors.Add(Floor.First, companiesFirst);
return new BuildingInformation(floors);
void AddEmployee(MyDictionary<BusinessCard, int> dic, int idx)
{
(var card, int room) = employees[idx];
dic.Add(card, room);
}
}
}

View file

@ -0,0 +1,52 @@
using BuildingDirectory.Model;
namespace BuildingDirectory.Test;
public sealed class BusinessCardTests
{
[Fact]
public void Equals_Equal()
{
BusinessCard[] cards = CreateSampleCards();
cards[0].Equals(cards[1]).Should().BeTrue("only name is considered");
cards[2].Equals(cards[3]).Should().BeTrue("only name is considered");
}
[Fact]
public void Equals_NotEqual()
{
BusinessCard[] cards = CreateSampleCards();
cards[1].Equals(cards[2]).Should().BeFalse("different people");
}
[Fact]
public void HashCode_Value()
{
var card = CreateSampleCards()[0];
card.GetHashCode()
.Should().Be(card.GetHashCode(), "has to return same value for same input every time");
}
[Fact]
public void HashCode_RelevantProperties()
{
BusinessCard[] cards = CreateSampleCards();
var card1 = cards[2];
var card2 = cards[3];
card1.GetHashCode()
.Should().Be(card2.GetHashCode(), "hash code only based on relevant properties");
}
private static BusinessCard[] CreateSampleCards() =>
[
new("Horst", "Fuchs", "Sales", null, "h.fuchs@foo.com"),
new("Horst", "Fuchs", "KeyAccounting", "1234", "horst.fuchs@foo.com"),
new("Susi", "Sonne", "Development", null, "s.sonne@foo.com"),
new("Susi", "Sonne", "Research", null, "s.sonne@foo.com")
];
}

View file

@ -0,0 +1,45 @@
using BuildingDirectory.Model;
namespace BuildingDirectory.Test;
public sealed class CompanyTests
{
private const string Name = "Foo Inc.";
[Fact]
public void Construction()
{
var company = new Company(CreateSampleDic(), Name);
company.Name.Should().Be(Name);
company.NoOfEmployees.Should().Be(2);
}
[Fact]
public void AskForRoom()
{
List<BusinessCard> employees = CreateSampleCards();
MyDictionary<BusinessCard, int>? dic = CreateSampleDic();
var company = new Company(dic, Name);
company.AskForRoom(employees[0]).Should().Be(101, "person B resides in room 101");
company.AskForRoom(employees[1]).Should().Be(202, "person C resides in room 202");
company.AskForRoom(new BusinessCard("X", "Y", "Z", null, "u@w.com"))
.Should().BeNull("person X is unknown");
}
private static List<BusinessCard> CreateSampleCards() =>
[
new("A", "B", "C", null, "B@D.at"),
new("A", "C", "C", null, "C@D.at")
];
private static MyDictionary<BusinessCard, int> CreateSampleDic()
{
List<BusinessCard> cards = CreateSampleCards();
var dic = new MyDictionary<BusinessCard, int>();
dic.Add(cards[0], 101);
dic.Add(cards[1], 202);
return dic;
}
}

View file

@ -0,0 +1,195 @@
namespace BuildingDirectory.Test;
public sealed class MyDictionaryTests
{
[Fact]
public void Add_Single()
{
const string Value = "test";
const int Key = 5;
var dic = new MyDictionary<int, string>();
dic.Add(Key, Value);
dic.Count.Should().Be(1, "single item added");
dic.ContainsKey(Key).Should().BeTrue();
}
[Fact]
public void Add_Single_Object()
{
var value = new Foo(6, "hello");
const int Key = 5;
var dic = new MyDictionary<int, Foo>();
dic.Add(Key, value);
dic.Count.Should().Be(1, "not only value but also reference types can be added");
dic.ContainsKey(Key).Should().BeTrue();
dic.TryGetValue(Key, out var returnedValue).Should().BeTrue();
returnedValue.Should().BeSameAs(value, "same reference returned");
}
[Fact]
public void Add_Single_Object_Null()
{
const int Key = 5;
var dic = new MyDictionary<int, Foo?>();
dic.Add(Key, null);
dic.Count.Should().Be(1, "null value can be valid if TValue is nullable");
dic.ContainsKey(Key).Should().BeTrue();
dic.TryGetValue(Key, out var returnedValue).Should().BeTrue();
returnedValue.Should().Be(null);
}
[Fact]
public void Add_Multiple()
{
int[] keys = [10, 11, 12];
bool[] values = [true, false, true];
var dic = new MyDictionary<int, bool>();
for (var i = 0; i < keys.Length; i++)
{
dic.Add(keys[i], values[i]);
}
dic.Count.Should().Be(3, "three different items added");
for (var i = 0; i < keys.Length; i++)
{
int key = keys[i];
dic.ContainsKey(key).Should().BeTrue();
dic.TryGetValue(key, out bool value).Should().BeTrue();
value.Should().Be(values[i], "values can occur more than once");
}
}
[Fact]
public void Add_Single_AlreadyExists()
{
const string Value1 = "test1";
const string Value2 = "test2";
const int Key = 5;
var dic = new MyDictionary<int, string>();
dic.Add(Key, Value1);
dic.Add(Key, Value2);
dic.Count.Should().Be(1, "single item added, then updated");
dic.ContainsKey(Key).Should().BeTrue();
dic.TryGetValue(Key, out string? value).Should().BeTrue("can be retrieved by key");
value.Should().Be(Value2, "initial value got updated");
}
[Fact]
public void GetKeys()
{
string[] keys = ["A", "B", "C"];
double[] values = [4D, 12.8D, -98.12D];
var dic = new MyDictionary<string, double>();
for (var i = 0; i < keys.Length; i++)
{
dic.Add(keys[i], values[i]);
}
dic.GetKeys().Should().Contain(keys, "contains all added keys in unspecified order")
.And.HaveCount(keys.Length);
}
[Fact]
public void GetValues()
{
string[] keys = ["A", "B", "C"];
double[] values = [4D, 12.8D, -98.12D];
var dic = new MyDictionary<string, double>();
for (var i = 0; i < keys.Length; i++)
{
dic.Add(keys[i], values[i]);
}
dic.GetValues().Should().Contain(values, "contains all added values in unspecified order")
.And.HaveCount(values.Length);
}
[Fact]
public void ContainsKey_NotExists()
{
var dic = new MyDictionary<int, decimal>();
dic.Add(44, 120M);
dic.ContainsKey(55).Should().BeFalse();
}
[Fact]
public void TryGetValue_NotExists()
{
var dic = new MyDictionary<int, decimal>();
dic.Add(44, 120M);
dic.TryGetValue(55, out decimal value).Should().BeFalse();
value.Should().Be(0, "if key not found assign default to out parameter");
}
[Fact]
public void Remove_Exists()
{
const int Key = 44;
var dic = new MyDictionary<int, decimal>();
dic.Add(Key, 120M);
dic.Remove(Key).Should().BeTrue("could be removed");
dic.Count.Should().Be(0, "count reduced due to removal");
}
[Fact]
public void Remove_NotExists()
{
var dic = new MyDictionary<int, decimal>();
dic.Add(44, 120M);
dic.Remove(1234).Should().BeFalse("key not present in dictionary");
dic.Count.Should().Be(1, "count unchanged");
}
[Fact]
public void Indexer_Exists()
{
const int Key = 44;
const decimal Value = 120M;
var dic = new MyDictionary<int, decimal>();
dic.Add(Key, Value);
dic[Key].Should().Be(Value, "indexer access is possible");
}
[Fact]
public void Indexer_NotExists_Value()
{
var dic = new MyDictionary<int, decimal>();
dic[44].Should().Be(0M, "not found so default decimal returned");
}
[Fact]
public void Indexer_NotExists_Object()
{
var dic = new MyDictionary<int, decimal?>();
dic[44].Should().BeNull("not found so default nullable returned");
}
private record Foo(int Bar, string Baz);
}

31
BuildingDirectory.sln Normal file
View file

@ -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}") = "BuildingDirectory", "BuildingDirectory\BuildingDirectory.csproj", "{7F30E637-BAFE-42FA-A173-F42B3902ED3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildingDirectory.Test", "BuildingDirectory.Test\BuildingDirectory.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

View file

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HTLLeonding.Utility.LeoAnalyzers" Version="1.0.2" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,46 @@
using BuildingDirectory.Model;
namespace BuildingDirectory;
/// <summary>
/// Represents a simple building information system
/// </summary>
public sealed class BuildingInformation
{
private readonly MyDictionary<Floor, MyDictionary<string, Company>> _companiesPerFloor;
/// <summary>
/// Creates a new instance with the supplied building information
/// </summary>
/// <param name="companiesPerFloor"></param>
public BuildingInformation(MyDictionary<Floor, MyDictionary<string, Company>> companiesPerFloor)
{
_companiesPerFloor = companiesPerFloor;
}
/// <summary>
/// Returns all floors in the building
/// </summary>
/// <returns>A list of available floors</returns>
// TODO
public List<Floor> GetFloorSelection() => new();
/// <summary>
/// Returns all companies residing on a specific floor.
/// If floor is not available null is returned.
/// </summary>
/// <param name="floor">The floor to check</param>
/// <returns>A list of names of companies residing on the provided floor</returns>
// TODO
public List<string>? GetCompaniesOnFloor(Floor floor) => null;
/// <summary>
/// Returns the company identified by floor and company name.
/// If the specified company is not found null is returned.
/// </summary>
/// <param name="floor">Floor to look at</param>
/// <param name="companyName">Name of the requested company</param>
/// <returns>Company information if found; null otherwise</returns>
// TODO
public Company? GetCompany(Floor floor, string companyName) => null;
}

View file

@ -0,0 +1,65 @@
namespace BuildingDirectory.Model;
/// <summary>
/// Represents a business card of a company employee
/// </summary>
public sealed class BusinessCard
{
/// <summary>
/// Creates a new instance of a business card
/// </summary>
/// <param name="firstName">First name of the employee</param>
/// <param name="lastName">Last name of the employee</param>
/// <param name="department">Department the employee works at</param>
/// <param name="phoneNumber">Phone number of the employee</param>
/// <param name="eMail">E-Mail address of the employee</param>
public BusinessCard(string firstName, string lastName, string department, string? phoneNumber, string eMail)
{
FirstName = firstName;
LastName = lastName;
Department = department;
PhoneNumber = phoneNumber;
EMail = eMail;
}
/// <summary>
/// Gets the first name of the employee
/// </summary>
public string FirstName { get; }
/// <summary>
/// Gets the last name of the employee
/// </summary>
public string LastName { get; }
/// <summary>
/// Gets the name of the department the employee works at
/// </summary>
public string Department { get; }
/// <summary>
/// Gets the phone number of the employee
/// </summary>
public string? PhoneNumber { get; }
/// <summary>
/// Gets the E-Mail address of the employee
/// </summary>
public string EMail { get; }
/// <summary>
/// Determines if this object is equal to another
/// </summary>
/// <param name="obj">Other object to compare to</param>
/// <returns>True if the objects are equal; false otherwise</returns>
// TODO
public override bool Equals(object? obj)
=> false;
/// <summary>
/// Calculates the hash code for this object
/// </summary>
/// <returns>Numeric hash code</returns>
// TODO
public override int GetHashCode() => -1;
}

View file

@ -0,0 +1,46 @@
namespace BuildingDirectory.Model;
/// <summary>
/// Represents a company with employees
/// </summary>
public sealed class Company
{
private readonly MyDictionary<BusinessCard, int> _employeeRooms;
/// <summary>
/// Creates a new instance of a company with the given name and the employees together
/// with their room numbers.
/// Employees are represented/identified by their <see cref="BusinessCard"/>
/// </summary>
/// <param name="employeeRooms">Employees and assigned room numbers</param>
/// <param name="name">Name of the company</param>
public Company(MyDictionary<BusinessCard, int> employeeRooms, string name)
{
_employeeRooms = employeeRooms;
Name = name;
}
/// <summary>
/// Gets the name of the company
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the number of employees working for the company
/// </summary>
// TODO
public int NoOfEmployees => -1;
/// <summary>
/// Returns the room number the clerk identified by the provided
/// <see cref="BusinessCard"/> resides at - if known
/// </summary>
/// <param name="businessCard">Identification of the employee</param>
/// <returns>Room number if found; null otherwise</returns>
public int? AskForRoom(BusinessCard businessCard)
{
// TODO
return -1;
}
}

View file

@ -0,0 +1,12 @@
namespace BuildingDirectory.Model;
/// <summary>
/// Represents a floor in a building
/// </summary>
public enum Floor
{
Ground,
First,
Second,
Third
}

View file

@ -0,0 +1,114 @@
using System.Diagnostics.CodeAnalysis;
namespace BuildingDirectory;
/// <summary>
/// A generic dictionary (hash map) implementation managing keys and associated values
/// </summary>
/// <typeparam name="TKey">Type of the keys; cannot be null</typeparam>
/// <typeparam name="TValue">Type of the values</typeparam>
public sealed class MyDictionary<TKey, TValue> where TKey : notnull
{
private const int InitialBuckets = 4;
private const int MaxDepth = 2;
// TODO
//private List<KeyValue>[] _buckets;
//private int _currentMaxDepth;
/// <summary>
/// Gets the count of items stored in this dictionary
/// </summary>
public int Count { get; private set; }
// TODO
private int NoOfBuckets => -1;
/// <summary>
/// Creates a new instance of the dictionary with default capacity
/// </summary>
public MyDictionary() : this(InitialBuckets)
{
}
private MyDictionary(int capacity)
{
// TODO
}
/// <summary>
/// Attempts to get the value associated with the given key.
/// Returns the default value for <see cref="TValue"/> if not found.
/// </summary>
/// <param name="key">Key of the required value</param>
// TODO
public TValue? this[TKey key] => default;
/// <summary>
/// Returns a list of all managed keys.
/// </summary>
/// <returns>A list of all keys</returns>
public List<TKey> GetKeys()
{
// TODO
return new();
}
/// <summary>
/// Returns a list of all managed values.
/// May contain duplicates.
/// </summary>
/// <returns>A list of all values</returns>
public List<TValue> GetValues()
{
// TODO
return new();
}
/// <summary>
/// Adds a value to the dictionary by its key.
/// If the key is already present the previous values is replaced.
/// </summary>
/// <param name="key">Key of the value</param>
/// <param name="value">Value to store</param>
public void Add(TKey key, TValue value)
{
// TODO
}
/// <summary>
/// Attempts to remove the given key and associated value from the dictionary.
/// Returns false if key is not found.
/// </summary>
/// <param name="key">Key to remove</param>
/// <returns>True if the key & value could be removed; false otherwise</returns>
public bool Remove(TKey key)
{
// TODO
return false;
}
/// <summary>
/// Attempts to get a value by the given key from the dictionary.
/// If not found value is set to the default of <see cref="TValue"/>.
/// </summary>
/// <param name="key">Key to look for</param>
/// <param name="value">Out param for storing value if found</param>
/// <returns>True if key was found; false otherwise</returns>
public bool TryGetValue(TKey key, out TValue? value)
{
// TODO
value = default;
return false;
}
/// <summary>
/// Checks if the dictionary contains the given key
/// </summary>
/// <param name="key">Key to search for</param>
/// <returns>True if key is found; false otherwise</returns>
public bool ContainsKey(TKey key)
{
// TODO
return false;
}
}

View file

@ -0,0 +1,5 @@
using System.Text;
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("*** BuildingDirectory ***");

183
readme.adoc Normal file
View file

@ -0,0 +1,183 @@
:sectnums:
:nofooter:
:toc: left
:icons: font
:data-uri:
:source-highlighter: highlightjs
:stem: latexmath
= Col.05 -- Building Directory
You are going to implement a building information system.
* Situation:
** A building has multiple floors
** On each floor none, one or more companies have their offices
** Each employee of a company has an office room assigned
* We are going to support this user story:
** You received a business card from a clerk and are now on your way to visit them at their office
** Enter the building, and you find an overview:
*** Which floors exist
*** Which companies are on each floor
** Select a floor and use the elevator to get there
** Once arrived at the selected floor follow the directions, labeled with the company names, to find the reception desk of the chosen company
** Hand the business card to the reception clerk who will then tell you the room number of the person you've come to visit
** Go to the room and conduct your business
* These scenarios will be implemented with a _generic dictionary (hash map)_
[plantuml]
----
@startuml
hide empty methods
class BusinessCard {
+string FirstName [readonly]
+string LastName [readonly]
+string Department [readonly]
+string? PhoneNumber [readonly]
+string EMail [readonly]
+BusinessCard(string, string, string, string?, string)
+bool Equals(object?) [override]
+int GetHashCode() [override]
-bool Equals(BusinessCard)
}
class Company {
-MyDictionary<BusinessCard, int> _employeeRooms [readonly]
+string Name [readonly]
+int NoOfEmployees [readonly]
+Company(MyDictionary<BusinessCard, int>, string)
+int? AskForRoom(BusinessCard)
}
enum Floor {
Ground
First
Second
Third
}
class BuildingInformation {
-MyDictionary<Floor, MyDictionary<string, Company>> _companiesPerFloor
+BuildingInformation(MyDictionary<Floor, MyDictionary<string, Company>>)
+List<Floor> GetFloorSelection()
+List<string>? GetCompaniesOnFloor(Floor)
+Company? GetCompany(Floor, string)
}
class MyDictionary<TKey, TValue> {
-int InitialBuckets [const]
-int MaxDepth [const]
-List<KeyValue>[] _buckets
-int _currentMaxDepth
-int NoOfBuckets [readonly]
+int Count [private set]
+TValue? this[TKey key]
+MyDictionary()
+MyDictionary(int)
+List<TKey> GetKeys()
+List<TValue> GetValues()
+void Add(TKey, TValue)
+bool Remove(TKey)
+bool TryGetValue(TKey, out TValue?)
+bool ContainsKey(TKey)
}
Company "1" -r- "n" BusinessCard
BuildingInformation "1" -- "n" Company
BuildingInformation -r- "n" Floor
@enduml
----
== Dictionary
* Remember: a dictionary, also called a _hash map_ is similar to a hash set
** The keys are unique
** The content is unordered
* Different from a hash set, a dictionary assigns a value to each key
** Values are added _with_ and _by_ their key
** Values are retrieved by their key
* A value is not constrained
** It can occur multiple times
** It can be any object
*** Not only a single value
*** But also a collection of other values
=== Example
[cols="1,5a"]
|===
|Class |Students
|2AHIF
|
[cols="1,2"]
!===
!ID !FirstName
!IF231234
!Anna
!IF231235
!Max
!IF231236
!Linda
!===
|2BHIF
|
[cols="1,2"]
!===
!ID !FirstName
!IF234321
!Anna
!IF234322
!Anna
!IF234323
!Max
!===
|===
* `Class` & `ID` have to be _unique_ (within one dictionary)
* `Students` & `FirstName` _can_ contain the same value multiple times
=== Implementation Hint
The following _private_ members might be helpful when you implement `MyDictionary`.
[source,csharp]
----
private bool TryGetValue(TKey key, out KeyValue? existingKeyValue) [...]
private List<KeyValue> GetKeysAndValues() [...]
private void Grow() [...]
private static List<KeyValue>[] CreateBuckets(int amount) [...]
private sealed class KeyValue <1>
{
public KeyValue(TKey key, TValue value)
{
Key = key;
Value = value;
}
public TKey Key { get; }
public TValue Value { get; set; }
private bool Equals(KeyValue other) [...]
public override bool Equals(object? obj) [...]
public override int GetHashCode() [...]
}
----
<1> If implemented _within_ `MyDictionary` the type parameters `TKey` & `TValue` will be taken from the surrounding class and _don't_ have to be specified
== Tasks
As usual:
* Implement missing code pieces marked with `TODO`
* Mind the provided XMLDoc & UnitTests