Initial commit

This commit is contained in:
github-classroom[bot] 2025-04-29 15:03:45 +00:00 committed by GitHub
commit b087f272b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 5345 additions and 0 deletions

3551
.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,76 @@
using Numbers.NumberFactory;
namespace Numbers.Test.NumberFactory;
public sealed class EvenNumbersTests
{
[Fact]
public void LowerBound()
{
const long LowerBound = 3;
var numbers = Factory.Create(NumberType.Even, LowerBound, 9);
numbers.LowerBound.Should().Be(LowerBound);
}
[Fact]
public void UpperBound()
{
const long UpperBound = 9;
var numbers = Factory.Create(NumberType.Even, 3, UpperBound);
numbers.UpperBound.Should().Be(UpperBound);
}
[Fact]
public void Count()
{
var numbers = Factory.Create(NumberType.Even, 3, 11);
numbers.Length.Should().Be(4, "4, 6, 8, 10");
}
[Fact]
public void FirstIndex()
{
var numbers = Factory.Create(NumberType.Even, 3, 9);
numbers[0].Should().Be(4);
}
[Fact]
public void LastIndex()
{
var numbers = Factory.Create(NumberType.Even, 3, 9);
numbers[^1].Should().Be(8);
}
[Theory]
[InlineData(NumberType.Even, 3, 9, new long[] { 4, 6, 8 })]
[InlineData(NumberType.Even, 2, 9, new long[] { 2, 4, 6, 8 })]
[InlineData(NumberType.Even, 3, 10, new long[] { 4, 6, 8, 10 })]
public void Content(NumberType type, long lower, long upper, long[] expected)
{
var numbers = Factory.Create(type, lower, upper);
for (var i = 0; i < numbers.Length; i++)
{
numbers[i].Should().Be(expected[i]);
}
}
[Fact]
public void ForeachContent()
{
int i = -1;
long[] expected = [4, 6, 8];
var numbers = Factory.Create(NumberType.Even, 3, 9);
foreach (long item in numbers)
{
i++;
item.Should().Be(expected[i]);
}
}
}

View file

@ -0,0 +1,18 @@
using Numbers.NumberFactory;
using Numbers.NumberFactory.NumberImpls;
namespace Numbers.Test.NumberFactory;
public sealed class FactoryTests
{
[Theory]
[InlineData(NumberType.Even, typeof(EvenNumbers))]
[InlineData(NumberType.Odd, typeof(OddNumbers))]
[InlineData(NumberType.Square, typeof(SquareNumbers))]
[InlineData(NumberType.Prime, typeof(PrimeNumbers))]
public void Create(NumberType type, Type expectedType)
{
Factory.Create(type, 0, 10)
.Should().BeOfType(expectedType);
}
}

View file

@ -0,0 +1,49 @@
using Numbers.NumberFactory;
namespace Numbers.Test.NumberFactory;
public sealed class NumbersEnumeratorTests
{
private readonly NumbersEnumerator _enumerator = new([1, 2, 3]);
[Fact]
public void MoveNext_StopWhenExhausted()
{
_enumerator.MoveNext().Should().BeTrue();
_enumerator.MoveNext().Should().BeTrue();
_enumerator.MoveNext().Should().BeTrue();
_enumerator.MoveNext().Should().BeFalse();
}
[Fact]
public void MoveNext_Simple()
{
_enumerator.MoveNext().Should().BeTrue("can move");
}
[Fact]
public void Current()
{
_enumerator.MoveNext();
_enumerator.Current.Should().Be(1);
_enumerator.MoveNext();
_enumerator.Current.Should().Be(2);
_enumerator.MoveNext();
_enumerator.Current.Should().Be(3);
}
[Fact]
public void Reset()
{
_enumerator.MoveNext();
_enumerator.MoveNext();
_enumerator.Reset();
_enumerator.Current.Should().Be(-1);
_enumerator.MoveNext();
_enumerator.Current.Should().Be(1);
}
}

View file

@ -0,0 +1,76 @@
using Numbers.NumberFactory;
namespace Numbers.Test.NumberFactory;
public sealed class OddNumberTests
{
[Fact]
public void LowerBound()
{
const long LowerBound = 4;
var numbers = Factory.Create(NumberType.Odd, LowerBound, 10);
numbers.LowerBound.Should().Be(LowerBound);
}
[Fact]
public void UpperBound()
{
const long UpperBound = 10;
var numbers = Factory.Create(NumberType.Odd, 4, UpperBound);
numbers.UpperBound.Should().Be(UpperBound);
}
[Fact]
public void Count()
{
var numbers = Factory.Create(NumberType.Odd, 4, 10);
numbers.Length.Should().Be(3, "5, 7, 9");
}
[Fact]
public void FirstIndex()
{
var numbers = Factory.Create(NumberType.Odd, 4, 10);
numbers[0].Should().Be(5);
}
[Fact]
public void LastIndex()
{
var numbers = Factory.Create(NumberType.Odd, 4, 10);
numbers[^1].Should().Be(9);
}
[Theory]
[InlineData(NumberType.Odd, 4, 10, new long[] { 5, 7, 9 })]
[InlineData(NumberType.Odd, 3, 10, new long[] { 3, 5, 7, 9 })]
[InlineData(NumberType.Odd, 4, 11, new long[] { 5, 7, 9, 11 })]
public void Content(NumberType type, long lower, long upper, long[] expected)
{
var numbers = Factory.Create(type, lower, upper);
for (var i = 0; i < numbers.Length; i++)
{
numbers[i].Should().Be(expected[i]);
}
}
[Fact]
public void ForeachContent()
{
int i = -1;
long[] expected = [5, 7, 9];
var numbers = Factory.Create(NumberType.Odd, 4, 10);
foreach (long item in numbers)
{
i++;
item.Should().Be(expected[i]);
}
}
}

View file

@ -0,0 +1,91 @@
using Numbers.NumberFactory;
namespace Numbers.Test.NumberFactory;
public sealed class PrimeNumbersTests
{
[Fact]
public void LowerBound()
{
const long LowerBound = 0;
var numbers = Factory.Create(NumberType.Prime, LowerBound, 10);
numbers.LowerBound.Should().Be(LowerBound);
}
[Fact]
public void UpperBound()
{
const long UpperBound = 10;
var numbers = Factory.Create(NumberType.Prime, 0, UpperBound);
numbers.UpperBound.Should().Be(UpperBound);
}
[Fact]
public void Count()
{
var numbers = Factory.Create(NumberType.Prime, 0, 10);
numbers.Length.Should().Be(4, "2, 3, 5, 7");
}
[Fact]
public void FirstIndex()
{
var numbers = Factory.Create(NumberType.Prime, 0, 10);
numbers[0].Should().Be(2);
}
[Fact]
public void LastIndex()
{
var numbers = Factory.Create(NumberType.Prime, 0, 10);
numbers[^1].Should().Be(7);
}
[Theory]
[InlineData(NumberType.Prime, 0, 10, new long[] { 2, 3, 5, 7 })]
[InlineData(NumberType.Prime, 2, 10, new long[] { 2, 3, 5, 7 })]
[InlineData(NumberType.Prime, 0, 7, new long[] { 2, 3, 5, 7 })]
public void Content(NumberType type, long lower, long upper, long[] expected)
{
var numbers = Factory.Create(type, lower, upper);
for (var i = 0; i < numbers.Length; i++)
{
numbers[i].Should().Be(expected[i]);
}
}
[Fact]
public void ForeachContent()
{
int i = -1;
long[] expected = [2, 3, 5, 7];
var numbers = Factory.Create(NumberType.Prime, 0, 10);
foreach (long item in numbers)
{
i++;
item.Should().Be(expected[i]);
}
}
[Fact]
public void ForeachContentExtended()
{
int i = -1;
long[] expected =
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97];
var numbers = Factory.Create(NumberType.Prime, 0, 100);
foreach (long item in numbers)
{
i++;
item.Should().Be(expected[i]);
}
}
}

View file

@ -0,0 +1,76 @@
using Numbers.NumberFactory;
namespace Numbers.Test.NumberFactory;
public sealed class SquareNumbersTests
{
[Fact]
public void LowerBound()
{
const long LowerBound = 4;
var numbers = Factory.Create(NumberType.Square, LowerBound, 10);
numbers.LowerBound.Should().Be(LowerBound);
}
[Fact]
public void UpperBound()
{
const long UpperBound = 10;
var numbers = Factory.Create(NumberType.Square, 4, UpperBound);
numbers.UpperBound.Should().Be(UpperBound);
}
[Fact]
public void Count()
{
var numbers = Factory.Create(NumberType.Square, 4, 10);
numbers.Length.Should().Be(2, "4, 9");
}
[Fact]
public void FirstIndex()
{
var numbers = Factory.Create(NumberType.Square, 4, 10);
numbers[0].Should().Be(4);
}
[Fact]
public void LastIndex()
{
var numbers = Factory.Create(NumberType.Square, 4, 10);
numbers[^1].Should().Be(9);
}
[Theory]
[InlineData(NumberType.Square, 4, 10, new long[] { 4, 9 })]
[InlineData(NumberType.Square, 3, 10, new long[] { 4, 9 })]
[InlineData(NumberType.Square, 4, 11, new long[] { 4, 9 })]
public void Content(NumberType type, long lower, long upper, long[] expected)
{
var numbers = Factory.Create(type, lower, upper);
for (var i = 0; i < numbers.Length; i++)
{
numbers[i].Should().Be(expected[i]);
}
}
[Fact]
public void ForeachContent()
{
int i = -1;
long[] expected = [4, 9];
var numbers = Factory.Create(NumberType.Square, 4, 10);
foreach (long item in numbers)
{
i++;
item.Should().Be(expected[i]);
}
}
}

View file

@ -0,0 +1,81 @@
using Numbers.NumberString;
namespace Numbers.Test.NumberString;
public sealed class DigitEnumeratorTests
{
[Fact]
public void MoveNext_NoMoreDigits()
{
var enumerator = new DigitEnumerator("abc");
enumerator.MoveNext().Should().BeFalse();
}
[Fact]
public void MoveNext_MoreDigits()
{
var enumerator = new DigitEnumerator("abc123");
enumerator.MoveNext().Should().BeTrue();
}
[Theory]
[MemberData(nameof(EnumeratorTestData))]
public void Current_CorrectDigit(string input, int moveSteps, int expectedDigit)
{
var enumerator = new DigitEnumerator(input);
enumerator.Current.Should().Be(-1, "enumerator not moved yet");
for (var i = 0; i < moveSteps; i++)
{
enumerator.MoveNext();
}
enumerator.Current.Should().Be(expectedDigit);
}
[Fact]
public void Reset()
{
var enumerator = new DigitEnumerator("abc123");
enumerator.MoveNext();
enumerator.MoveNext();
enumerator.Reset();
enumerator.MoveNext();
enumerator.Current.Should().Be(1);
}
[Fact]
public void Interface()
{
var enumerator = new DigitEnumerator("abc123");
enumerator.Should().BeAssignableTo<IEnumerator<int>>();
}
public static TheoryData<string, int, int> EnumeratorTestData() =>
new()
{
{ "abc132", 1, 1 },
{ "abc132", 2, 3 },
{ "abc132", 3, 2 },
{ "1abc23", 1, 1 },
{ "1abc23", 2, 2 },
{ "1abc23", 3, 3 },
{ "a1b2c3", 1, 1 },
{ "a1b2c3", 2, 2 },
{ "a1b2c3", 3, 3 },
{ "123abc", 1, 1 },
{ "123abc", 2, 2 },
{ "123abc", 3, 3 },
{ "2def456", 1, 2 },
{ "2def456", 2, 4 },
{ "2def456", 3, 5 },
{ "d4ef56", 1, 4 },
{ "d4ef56", 2, 5 },
{ "d4ef56", 3, 6 }
};
}

View file

@ -0,0 +1,135 @@
using System.Collections;
using Numbers.NumberString;
namespace Numbers.Test.NumberString;
using NumberString = Numbers.NumberString.NumberString;
public sealed class NumberStringTests
{
[Theory]
[InlineData("abc133", 133)]
[InlineData("4a2b3c", 423)]
[InlineData("1235abc", 1235)]
[InlineData("3ab2c", 32)]
[InlineData("abc", 0)]
public void NumericValue(string input, int expected)
{
var numberString = new NumberString(input);
numberString.NumericValue.Should().Be(expected);
}
[Fact]
public void Equals_Equal()
{
var numberString1 = new NumberString("abc123");
var numberString2 = new NumberString("1a2b3c");
numberString1.Should<NumberString>().Be(numberString2);
}
[Fact]
public void Equals_NotEqual()
{
var numberString1 = new NumberString("abc123");
var numberString2 = new NumberString("abc124");
numberString1.Should<NumberString>().NotBe(numberString2);
}
[Fact]
public void OperatorGreater()
{
var numberString1 = new NumberString("abc124");
var numberString2 = new NumberString("abc123");
(numberString1 > numberString2).Should().BeTrue();
}
[Fact]
public void OperatorLess()
{
var numberString1 = new NumberString("abc123");
var numberString2 = new NumberString("abc124");
(numberString1 < numberString2).Should().BeTrue();
}
[Fact]
public void CompareTo()
{
var numberString1 = new NumberString("abc123");
var numberString2 = new NumberString("abc124");
var numberString3 = new NumberString("abc124");
numberString1.CompareTo(numberString2).Should().BeNegative();
numberString2.CompareTo(numberString1).Should().BePositive();
numberString2.CompareTo(numberString3).Should().Be(0);
}
[Fact]
public void Equals_Operators()
{
var numberString1 = new NumberString("abc123");
var numberString2 = new NumberString("abc124");
var numberString3 = new NumberString("abc124");
(numberString1 == numberString2).Should().BeFalse();
(numberString2 == numberString3).Should().BeTrue();
(numberString1 != numberString3).Should().BeTrue();
(numberString2 != numberString3).Should().BeFalse();
}
[Fact]
public void Equals_Simple()
{
var numberString1 = new NumberString("abc123");
var numberString2 = new NumberString("abc123");
numberString1.Equals(new object()).Should().BeFalse();
numberString1.Equals(null).Should().BeFalse();
numberString1!.Equals(numberString2).Should().BeTrue();
}
[Fact]
public void GetEnumerator()
{
var numberString = new NumberString("abc123");
IEnumerator<int> enumerator = numberString.GetEnumerator();
enumerator.Should().BeOfType<DigitEnumerator>();
enumerator.Dispose();
}
[Fact]
public void GetEnumerator_Redirect()
{
var numberString = new NumberString("abc123");
IEnumerator enumerator = numberString.GetEnumerator();
enumerator.Should().BeOfType<DigitEnumerator>();
((IDisposable) enumerator).Dispose();
}
[Fact]
public void HashCode()
{
var numberString = new NumberString("abc123");
numberString.GetHashCode().Should().Be(numberString.NumericValue);
}
[Fact]
public void Indexer()
{
var numberString = new NumberString("5abc1de7fg34");
numberString[0].Should().Be(5);
numberString[2].Should().Be(7);
numberString[4].Should().Be(4);
numberString[-1].Should().Be(-1, "invalid index");
numberString[5].Should().Be(-1, "invalid index (too high)");
}
}

View file

@ -0,0 +1,37 @@
<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" />
<Using Include="NSubstitute" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AwesomeAssertions" Version="8.0.2" />
<PackageReference Include="HTLLeonding.Utility.LeoAnalyzers" Version="1.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Numbers\Numbers.csproj" />
</ItemGroup>
</Project>

31
Numbers.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}") = "Numbers", "Numbers\Numbers.csproj", "{7F30E637-BAFE-42FA-A173-F42B3902ED3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Numbers.Test", "Numbers.Test\Numbers.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,56 @@
using System.Collections;
namespace Numbers.NumberFactory;
/// <summary>
/// Base class for <see cref="INumbers" /> implementations
/// </summary>
/// <inheritdoc cref="INumbers" />
public abstract class AbstractNumbers : INumbers
{
// TODO
// private readonly List<long> _list;
/// <summary>
/// Creates a new instance of <see cref="AbstractNumbers" /> with the given bounds.
/// Will generate the appropriate numbers based on the implementation of <see cref="PickNumber" />.
/// </summary>
/// <param name="lowerBound">Lower bound of the number range</param>
/// <param name="upperBound">Upper bound of the number range</param>
protected AbstractNumbers(long lowerBound, long upperBound)
{
// TODO
}
public long this[int index]
{
get
{
// TODO
return -1L;
}
}
public long LowerBound { get; }
public long UpperBound { get; }
public int Length => -1; // TODO
public IEnumerator<long> GetEnumerator() => null!; // TODO
IEnumerator IEnumerable.GetEnumerator() => null!; // TODO
private void GenerateNumbers()
{
// TODO
}
/// <summary>
/// Determines whether the given number should be picked based on the task of a
/// specific <see cref="INumbers" /> implementation
/// </summary>
/// <param name="number">Number to check</param>
/// <returns>True if the number should be picked; false otherwise</returns>
protected abstract bool PickNumber(long number);
}

View file

@ -0,0 +1,33 @@
using Numbers.NumberFactory.NumberImpls;
namespace Numbers.NumberFactory;
/// <summary>
/// A factory that creates <see cref="INumbers" /> instances
/// </summary>
public static class Factory
{
/// <summary>
/// Creates an <see cref="INumbers" /> instance for the given <paramref name="type" />
/// </summary>
/// <param name="type">Number factory type to create</param>
/// <param name="lowerBound">Lower bound of the number range</param>
/// <param name="upperBound">Upper bound of the number range</param>
/// <returns>Appropriate instance or null if requested type is unknown</returns>
public static INumbers Create(NumberType type, long lowerBound, long upperBound)
{
// TODO
return null!;
}
}
/// <summary>
/// Possible number factory types
/// </summary>
public enum NumberType
{
Even,
Odd,
Square,
Prime
}

View file

@ -0,0 +1,29 @@
namespace Numbers.NumberFactory;
/// <summary>
/// Defines the ability to pick numbers which fulfill a certain condition from within a specified range
/// </summary>
/// <inheritdoc cref="IEnumerable{T}" />
public interface INumbers : IEnumerable<long>
{
/// <summary>
/// Gets the n-th matching number in the range
/// </summary>
/// <param name="index">Index of the n-th number</param>
public long this[int index] { get; }
/// <summary>
/// Gets the lower bound of the number range
/// </summary>
public long LowerBound { get; }
/// <summary>
/// Gets the upper bound of the number range
/// </summary>
public long UpperBound { get; }
/// <summary>
/// Gets the amount of (filtered) numbers in the range
/// </summary>
public int Length { get; }
}

View file

@ -0,0 +1,10 @@
namespace Numbers.NumberFactory.NumberImpls;
/// <summary>
/// An <see cref="AbstractNumbers" /> implementation that picks even numbers
/// </summary>
/// <inheritdoc cref="AbstractNumbers" />
public sealed class EvenNumbers(long lowerBound, long upperBound) : AbstractNumbers(lowerBound, upperBound)
{
protected override bool PickNumber(long number) => false; // TODO
}

View file

@ -0,0 +1,10 @@
namespace Numbers.NumberFactory.NumberImpls;
/// <summary>
/// An <see cref="AbstractNumbers" /> implementation that picks odd numbers
/// </summary>
/// <inheritdoc cref="AbstractNumbers" />
public sealed class OddNumbers(long lowerBound, long upperBound) : AbstractNumbers(lowerBound, upperBound)
{
protected override bool PickNumber(long number) => false; // TODO
}

View file

@ -0,0 +1,10 @@
namespace Numbers.NumberFactory.NumberImpls;
/// <summary>
/// An <see cref="AbstractNumbers" /> implementation that picks prime numbers
/// </summary>
/// <inheritdoc cref="AbstractNumbers" />
public sealed class PrimeNumbers(long lowerBound, long upperBound) : AbstractNumbers(lowerBound, upperBound)
{
protected override bool PickNumber(long number) => false; // TODO
}

View file

@ -0,0 +1,10 @@
namespace Numbers.NumberFactory.NumberImpls;
/// <summary>
/// An <see cref="AbstractNumbers" /> implementation that picks square numbers
/// </summary>
/// <inheritdoc cref="AbstractNumbers" />
public sealed class SquareNumbers(long lowerBound, long upperBound) : AbstractNumbers(lowerBound, upperBound)
{
protected override bool PickNumber(long number) => false; // TODO
}

View file

@ -0,0 +1,39 @@
using System.Collections;
namespace Numbers.NumberFactory;
/// <summary>
/// An enumerator for a list of numbers
/// </summary>
/// <inheritdoc cref="IEnumerator{T}" />
public sealed class NumbersEnumerator : IEnumerator<long>
{
/// <summary>
/// Creates a new instance of <see cref="NumbersEnumerator" /> which will enumerate over the given list
/// </summary>
/// <param name="list">List to iterate over</param>
public NumbersEnumerator(List<long> list)
{
// TODO
List = null!;
}
private int Index { get; set; }
private List<long> List { get; }
public long Current => -1L; // TODO
public void Dispose()
{
// TODO?
}
object IEnumerator.Current => null!; // TODO
public bool MoveNext() => false; // TODO
public void Reset()
{
// TODO
}
}

View file

@ -0,0 +1,53 @@
using System.Collections;
namespace Numbers.NumberString;
/// <summary>
/// Allows to enumerate only the digits in a string
/// </summary>
/// <inheritdoc cref="IEnumerator{T}"/>
public sealed class DigitEnumerator : IEnumerator<int>
{
// TODO
// private readonly string _text;
// private int _index;
/// <summary>
/// Creates a new instance of <see cref="DigitEnumerator"/> based on the given text.
/// This will be called within <see cref="NumberString.GetEnumerator"/>.
/// </summary>
/// <param name="text">The text to iterate over (containing both letters and digits)</param>
public DigitEnumerator(string text)
{
// TODO
}
public bool MoveNext()
{
// TODO
return false;
}
public void Reset()
{
// TODO
}
public int Current
{
get
{
// TODO
return -1;
//static int GetDigit(char c) => c - '0';
}
}
object IEnumerator.Current => null!; // TODO
public void Dispose()
{
// TODO?
}
}

View file

@ -0,0 +1,66 @@
using System.Collections;
namespace Numbers.NumberString;
/// <summary>
/// Represents a string consisting of digits and letters intermixed
/// </summary>
/// <inheritdoc cref="IEnumerable{T}" />
/// <inheritdoc cref="IComparable{T}" />
public sealed class NumberString : IEnumerable<int>, IComparable<NumberString>
{
// TODO
// private readonly string _text;
// private int? _numericValue;
/// <summary>
/// Creates a new instance of <see cref="NumberString" /> based on the given text.
/// As a simplification, the text is assumed to be a valid number string.
/// </summary>
/// <param name="text">A string containing digits and letters</param>
public NumberString(string text)
{
// TODO
}
/// <summary>
/// Gets the numeric value contained in the number string
/// </summary>
public int NumericValue => -1; // TODO
/// <summary>
/// Gets the digit at the given index.
/// If the index is out of range, -1 is returned.
/// </summary>
/// <param name="index">Digit index</param>
public int this[int index]
{
get
{
// TODO
return -1;
}
}
public int CompareTo(NumberString? other) => -1; // TODO
public IEnumerator<int> GetEnumerator() => null!; // TODO
IEnumerator IEnumerable.GetEnumerator() => null!; // TODO
public override bool Equals(object? obj)
{
// TODO
return false;
}
public override int GetHashCode() => -1; // TODO
public static bool operator >(NumberString a, NumberString b) => false; // TODO
public static bool operator <(NumberString a, NumberString b) => false; // TODO
public static bool operator ==(NumberString a, NumberString b) => false; // TODO
public static bool operator !=(NumberString a, NumberString b) => false; // TODO
}

15
Numbers/Numbers.csproj Normal file
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>

46
Numbers/Program.cs Normal file
View file

@ -0,0 +1,46 @@
using System.Text;
using Numbers.NumberFactory;
using Numbers.NumberString;
Console.OutputEncoding = Encoding.UTF8;
NumberStringRun();
Console.WriteLine(Environment.NewLine);
NumberFactoryRun();
return;
static void NumberStringRun()
{
Console.WriteLine("*** NumberString ***");
Console.WriteLine("Extract digits from strings");
Console.Write("Enter text: ");
var numberString = new NumberString(Console.ReadLine()!);
Console.Write($"Contained digits: {string.Join(" ", numberString)}");
Console.WriteLine();
Console.Write("Press any key to continue...");
Console.ReadKey();
}
static void NumberFactoryRun()
{
var evenRange = (Lower: 3, Upper: 9);
var primeRange = (Lower: 1, Upper: 15);
Console.WriteLine("*** NumberFactory ***");
var evenNumbers = Factory.Create(NumberType.Even, evenRange.Lower, evenRange.Upper);
Console.WriteLine($"Even numbers between {evenRange.Lower} and {evenRange.Upper}: " +
$"{Format(evenNumbers)}{Environment.NewLine}");
var primeNumbers = Factory.Create(NumberType.Prime, primeRange.Lower, primeRange.Upper);
Console.Write($"Prime numbers between {primeRange.Lower} and {primeRange.Upper}: " +
$"{Format(primeNumbers)}{Environment.NewLine}");
Console.Write("Press any key to exit...");
Console.ReadKey();
return;
static string Format(IEnumerable<long> numbers) => string.Join(" ", numbers);
}

BIN
pics/sample_run.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

164
readme.adoc Normal file
View file

@ -0,0 +1,164 @@
:sectnums:
:nofooter:
:toc: left
:icons: font
:data-uri:
:source-highlighter: highlightjs
:stem: latexmath
= Int.05 -- Numbers
This is a simple training exercise without any interesting background story 😉
== `NumberString`
Given a string consisting of numbers and digits in any order and combination, the goal is to extract all numbers while preserving order.
This extraction is supposed to happen by means of enumeration.
[plantuml]
----
@startuml
hide empty methods
hide empty fields
class DigitEnumerator <<sealed>> {
-string _text [readonly]
-int _index
+DigitEnumerator(string)
}
interface IEnumerator<int> {}
class NumberString <<sealed>> {
-string _text [readonly]
-int? _numericValue
+int NumericValue [readonly]
+int this[int] [readonly]
+NumberString(string)
}
interface IEnumerable<int> {}
interface IComparable<NumberString> {}
IEnumerator <|.. DigitEnumerator
IEnumerable <|.. NumberString
IComparable <|.. NumberString
@enduml
----
=== `DigitEnumerator`
* Enumerates digits in a `NumberString`
** Example: `a3bc1jki45u` => `3`, `1`, `4`, `5`
* Ignores all non-digit characters (letters)
* Implements the enumerator pattern
=== `NumberString`
* A string containing both letters and digits
* Can be enumerated
** Using a `DigitEnumerator`
* Can be compared
** By directly implementing `IComparable` -- no comparer required
** Comparison is based on the `NumericValue`
* Has a `NumericValue` property which represents the number encoded in the string
** Example: `a3bc1jki45u` => `3145`
** This property is _cached_ => only calculate at first call!
* It additionally has an _indexer_
** Accesses only the _digits_
*** Utilizing the enumerator
** Example: `a1bc3jki45u`, assessing index 2 => `3`
* Make sure to implement all _equality_ members including operators & hash code
== `NumberFactory`
A kind of digital factory which is able to give us a _something_ (a `INumbers` instance) which provides numbers which comply to a certain condition and are within the specified range.
[plantuml]
----
@startuml
hide empty methods
hide empty fields
class EvenNumbers <<sealed>> {}
class OddNumbers <<sealed>> {}
class PrimeNumbers <<sealed>> {}
class SquareNumbers <<sealed>> {}
abstract class AbstractNumbers {
-List<long> _list;
#AbstractNumbers(long, long)
{abstract} #bool PickupNumber(long)
-void GenerateNumbers()
}
interface INumbers {
+long this[int] [readonly]
+long LowerBound [readonly]
+long UpperBound [readonly]
+int Length [readonly]
}
interface IEnumerable<long> {}
interface IEnumerator<long> {}
enum NumberType {
Even
Odd
Square
Prime
}
static class Factory {
{static} +INumbers Create(NumberType, long, long)
}
class NumbersEnumerator <<sealed>> {
-int Index
-List<long> List [readonly]
+NumbersEnumerator(List<long>)
}
AbstractNumbers <|-- EvenNumbers
AbstractNumbers <|-- OddNumbers
AbstractNumbers <|-- PrimeNumbers
AbstractNumbers <|-- SquareNumbers
INumbers <|.. AbstractNumbers
IEnumerable <|.. INumbers
IEnumerator <|.. NumbersEnumerator
@enduml
----
=== `NumbersEnumerator`
* A very straight forward implementation of an `IEnumerator` for (`long`) numbers
* `Current` should return `-1` if the position is invalid
=== `INumbers`
* Defines the lower and upper bound of the range the numbers are picked from
* Ensures that implementations will provide an indexer for accessing the n-th number fulfilling the specific condition in the range
* Allows to get the total amount of number which fulfill the requirements
* Implements `IEnumerable<long>`
** We are using `long` instead of `int` to allow for very big numbers as well 😉
=== `AbstractNumbers`
* A base class for all number generators providing some common, shared implementations
* The indexer should return `-1` for invalid index values
* Make use of the `NumbersEnumerator`
* `GenerateNumbers` internally calls `PickNumber` which will be implemented by subclasses to decide which numbers to pick -- this check will be done for each number in the specified range
** Picked numbers should be stored in the list
* Hint: The constructor should call `GenerateNumbers`
=== Generator Implementations
* `EvenNumbers`: selects even numbers in the given range
* `OddNumbers`: selects odd numbers in the given range
* `PrimeNumbers`: selects prime numbers in the given range
** A simple loop-based implementation (just as we have implemented in first grade) is sufficient, but don't be overly wasteful with system resources
* `SquareNumbers`: selects square numbers in the given range
** https://en.wikipedia.org/wiki/Square_number[What is a square number]
=== `Factory`
* A static factory class which is able to create instances of `INumbers` implementations
* If an unknown `NumberType` is specified, return `null`
** Hint: to do that you'll need the `!` operator which is fine in this one case 😉
== Sample Run
image::pics/sample_run.png[Sample Run]