diff --git a/Functions/Functions.Tests/Functions.Tests.csproj b/Functions/Functions.Tests/Functions.Tests.csproj new file mode 100644 index 0000000..12e42e8 --- /dev/null +++ b/Functions/Functions.Tests/Functions.Tests.csproj @@ -0,0 +1,28 @@ + + + + net9.0 + latest + enable + enable + + true + + + + + + + + + + + + + + + + + + + diff --git a/Functions/Functions.Tests/FunctionsTests.cs b/Functions/Functions.Tests/FunctionsTests.cs new file mode 100644 index 0000000..134f2e6 --- /dev/null +++ b/Functions/Functions.Tests/FunctionsTests.cs @@ -0,0 +1,94 @@ +// +// Copyright (c) Ilya Krivtsov. All rights reserved. +// + +namespace Functions.Tests; + +public class FunctionsTests +{ + private static readonly IEnumerable NumberSequence = Enumerable.Range(0, 120); + + [Test] + public void Map_ConvertsNumbersToString_SameAs_Select() + { + AssertMapBehavesSameAsSelect(NumberSequence, x => x.ToString()); + } + + [Test] + public void Map_ConvertsStringToNumbers_SameAs_Select() + { + AssertMapBehavesSameAsSelect(NumberSequence.Select(x => x.ToString()), int.Parse); + } + + [Test] + public void Filter_SelectsNumbers_SameAs_Where() + { + AssertFilerBehavesSameAsWhere(NumberSequence, x => x % 2 == 0); + } + + [Test] + public void Filter_SelectsStrings_SameAs_Where() + { + AssertFilerBehavesSameAsWhere(NumberSequence.Select(x => x.ToString()), x => x.Contains('4')); + } + + [Test] + public void Fold_SumsNumbers_SameAsAggregate() + { + static int Sum(int x, int y) => x + y; + + var aggregateResult = NumberSequence.Aggregate(Sum); + var foldResult = NumberSequence.Fold(Sum); + + Assert.That(aggregateResult, Is.EqualTo(foldResult)); + } + + [Test] + public void Fold_AddsCharacters_SameAsAggregate() + { + static string Sum(string x, char y) => x + y; + + var source = Enumerable.Range('0', '~' - '0').Select(x => (char)x); + + var aggregateResult = source.Aggregate(string.Empty, Sum); + var foldResult = source.Fold(string.Empty, Sum); + + Assert.That(aggregateResult, Is.EqualTo(foldResult)); + } + + [Test] + public void Fold_AddsCharactersAndReverses_SameAsAggregate() + { + static string Sum(string x, char y) => x + y; + static string Reverse(string s) => string.Concat(s.Reverse()); + + var source = Enumerable.Range('0', '~' - '0').Select(x => (char)x); + + var aggregateResult = source.Aggregate(string.Empty, Sum, Reverse); + var foldResult = source.Fold(string.Empty, Sum, Reverse); + + Assert.That(aggregateResult, Is.EqualTo(foldResult)); + } + + [Test] + public void Fold_Throws_OnEmptyCollection() + { + Assert.Throws(() => Enumerable.Range(0, 0).Fold((x, y) => x + y)); + } + + private static void AssertMapBehavesSameAsSelect(IEnumerable source, Func map) + { + var selectResult = source.Select(map); + var mapResult = source.Map(map); + + Assert.That(selectResult.SequenceEqual(mapResult), Is.True); + } + + private static void AssertFilerBehavesSameAsWhere(IEnumerable source, Func filter) + { + var whereResult = source.Where(filter); + var filterResult = source.Filter(filter); + + Assert.That(whereResult.SequenceEqual(filterResult), Is.True); + } +} diff --git a/Functions/Functions.Tests/GlobalSuppressions.cs b/Functions/Functions.Tests/GlobalSuppressions.cs new file mode 100644 index 0000000..909e036 --- /dev/null +++ b/Functions/Functions.Tests/GlobalSuppressions.cs @@ -0,0 +1,11 @@ +// +// Copyright (c) Ilya Krivtsov. All rights reserved. +// + +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "This is tests project")] diff --git a/Functions/Functions.sln b/Functions/Functions.sln new file mode 100644 index 0000000..a19ec08 --- /dev/null +++ b/Functions/Functions.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Functions", "Functions\Functions.csproj", "{E71A4E78-9D6C-4616-9F99-3B067B9B92F3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Functions.Tests", "Functions.Tests\Functions.Tests.csproj", "{D1AAD59B-65F4-4F94-B960-24C9DE9C1BB8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E71A4E78-9D6C-4616-9F99-3B067B9B92F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E71A4E78-9D6C-4616-9F99-3B067B9B92F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E71A4E78-9D6C-4616-9F99-3B067B9B92F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E71A4E78-9D6C-4616-9F99-3B067B9B92F3}.Release|Any CPU.Build.0 = Release|Any CPU + {D1AAD59B-65F4-4F94-B960-24C9DE9C1BB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1AAD59B-65F4-4F94-B960-24C9DE9C1BB8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1AAD59B-65F4-4F94-B960-24C9DE9C1BB8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1AAD59B-65F4-4F94-B960-24C9DE9C1BB8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Functions/Functions/Functions.cs b/Functions/Functions/Functions.cs new file mode 100644 index 0000000..a640461 --- /dev/null +++ b/Functions/Functions/Functions.cs @@ -0,0 +1,114 @@ +// +// Copyright (c) Ilya Krivtsov. All rights reserved. +// + +namespace Functions; + +/// +/// Utility class that contains Map, Filter and Fold functions. +/// +public static class Functions +{ + /// + /// Maps all elements from to elements of type using function. + /// + /// Source type. + /// Type to map to. + /// Source to map. + /// Function that maps to . + /// Sequence of mapped elements. + public static IEnumerable Map(this IEnumerable source, Func map) + { + foreach (var item in source) + { + yield return map(item); + } + } + + /// + /// Filters elements from that satisfy . + /// + /// Type of elements. + /// Source to filter. + /// Function that filters elements; if it returns , adds element to the result, otherwise, doesn't. + /// Sequence of filtered elements. + public static IEnumerable Filter(this IEnumerable source, Func predicate) + { + foreach (var item in source) + { + if (predicate(item)) + { + yield return item; + } + } + } + + /// + /// Folds all elements in into one element. + /// + /// Type of elements. + /// Source to fold. + /// Function that is used to accumulate result. + /// Accumulated value. + /// Sequence is empty. + public static T Fold(this IEnumerable source, Func folder) + { + var enumerator = source.GetEnumerator(); + + if (!enumerator.MoveNext()) + { + throw new InvalidOperationException("No elements in source"); + } + + var value = enumerator.Current; + + while (enumerator.MoveNext()) + { + value = folder(value, enumerator.Current); + } + + return value; + } + + /// + /// Folds all elements in into one element. + /// + /// Type of elements in . + /// The type of accumulated value. + /// Source to fold. + /// Initial value of accumulated value. + /// Function that is used to accumulate result. + /// Accumulated value. + public static TResult Fold(this IEnumerable source, TResult initialValue, Func folder) + { + var value = initialValue; + foreach (var item in source) + { + value = folder(value, item); + } + + return value; + } + + /// + /// Folds all elements in into one element. + /// + /// Type of elements in . + /// The type of accumulated value. + /// The result type. + /// Source to fold. + /// Initial value of accumulated value. + /// Function that is used to accumulate result. + /// Function that is used to convert accumulated result. + /// Converted accumulated value. + public static TResult Fold(this IEnumerable source, TIntermediate initialValue, Func folder, Func map) + { + var value = initialValue; + foreach (var item in source) + { + value = folder(value, item); + } + + return map(value); + } +} diff --git a/Functions/Functions/Functions.csproj b/Functions/Functions/Functions.csproj new file mode 100644 index 0000000..bf05b58 --- /dev/null +++ b/Functions/Functions/Functions.csproj @@ -0,0 +1,10 @@ + + + + Library + net9.0 + enable + enable + + +