├── signing.snk ├── .nuget ├── NuGet.exe ├── NuGet.Config └── NuGet.targets ├── docs └── Sequence.Docs │ ├── icons │ └── Help.png │ ├── Media │ └── pascals-triangle.png │ ├── Content │ ├── VersionHistory │ │ ├── v1.0.0.0.aml │ │ ├── VersionHistory.aml │ │ └── v1.0.1.0.aml │ ├── Welcome.aml │ └── Examples.aml │ ├── ContentLayout.content │ └── Sequence.Docs.shfbproj ├── tests ├── Sequences.Tests.Functional │ ├── packages.config │ ├── Extensions │ │ └── TupleEx.cs │ ├── TriangularNumbers.cs │ ├── LuckyNumbers.cs │ ├── Fibonacci.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Factorials.cs │ ├── PrimeNumbers.cs │ ├── PascalsTriangle.cs │ └── Sequences.Tests.Functional.csproj └── Sequences.Tests.Unit │ ├── packages.config │ ├── SplitAtTests.cs │ ├── SpanTests.cs │ ├── PartitionTests.cs │ ├── StringTests.cs │ ├── EmptySequenceTests.cs │ ├── InitTests.cs │ ├── PadToTests.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── SlicingTests.cs │ ├── GroupedTests.cs │ ├── PatchTests.cs │ ├── PermutationsTests.cs │ ├── FlattenTests.cs │ ├── CombinationTests.cs │ ├── LengthTests.cs │ ├── TailTests.cs │ ├── CopyToTests.cs │ ├── ZipTests.cs │ ├── SlidingTests.cs │ ├── AddRemoveTests.cs │ ├── KmpSearchTests.cs │ ├── AggregationTests.cs │ ├── IndexTests.cs │ ├── FactoryMethodsTests.cs │ ├── SequenceBuilderTests.cs │ ├── Sequences.Tests.Unit.csproj │ ├── EnumerableTests.cs │ └── ExtensionMethodsTests.cs ├── src └── Sequences │ ├── EnumeratorEx.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── EmptySequence`1.cs │ ├── KmpSearchAlgorithm.cs │ ├── Sequences.csproj │ ├── SequenceBuilder.cs │ ├── Sequence`1.Iterators.cs │ └── Sequence.cs ├── dist └── Sequences.1.0.0.nuspec ├── .gitignore ├── Sequences.sln └── README.md /signing.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcastro/Sequences/HEAD/signing.snk -------------------------------------------------------------------------------- /.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcastro/Sequences/HEAD/.nuget/NuGet.exe -------------------------------------------------------------------------------- /docs/Sequence.Docs/icons/Help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcastro/Sequences/HEAD/docs/Sequence.Docs/icons/Help.png -------------------------------------------------------------------------------- /docs/Sequence.Docs/Media/pascals-triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcastro/Sequences/HEAD/docs/Sequence.Docs/Media/pascals-triangle.png -------------------------------------------------------------------------------- /.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Functional/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Functional/Extensions/TupleEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Sequences.Tests.Functional.Extensions 8 | { 9 | public static class TupleEx 10 | { 11 | public static int Sum(this Tuple tuple) 12 | { 13 | return tuple.Item1 + tuple.Item2; 14 | } 15 | 16 | public static int Product(this Tuple tuple) 17 | { 18 | return tuple.Item1 * tuple.Item2; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/SplitAtTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class SplitAtTests 11 | { 12 | [Fact] 13 | public void SplitAt_SplitsAtSelectedIndex() 14 | { 15 | var sequence = Sequence.Range(1, 6); 16 | var parts = sequence.SplitAt(3); 17 | 18 | int[] expectedFirst = {1, 2, 3}; 19 | int[] expectedSecond = {4, 5}; 20 | 21 | Assert.Equal(expectedFirst, parts.Item1); 22 | Assert.Equal(expectedSecond, parts.Item2); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/SpanTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class SpanTests 11 | { 12 | [Fact] 13 | public void Span_SplitsIntoPrefixAndSuffix() 14 | { 15 | var sequence = Sequence.Range(1, 11); 16 | var parts = sequence.Span(e => e <= 5); 17 | 18 | int[] expectedFirst = {1, 2, 3, 4, 5}; 19 | int[] expectedSecond = {6, 7, 8, 9, 10}; 20 | 21 | Assert.Equal(expectedFirst, parts.Item1); 22 | Assert.Equal(expectedSecond, parts.Item2); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/PartitionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class PartitionTests 11 | { 12 | [Fact] 13 | public void Partition_SelectsElementsUsingAPredicate() 14 | { 15 | var sequence = Sequence.From(1); 16 | var parts = sequence.Partition(e => e%2 == 0); 17 | 18 | int[] expectedFirst = {2, 4, 6, 8, 10}; 19 | int[] expectedSecond = {1, 3, 5, 7, 9}; 20 | 21 | Assert.Equal(expectedFirst, parts.Item1.Take(5)); 22 | Assert.Equal(expectedSecond, parts.Item2.Take(5)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/Sequence.Docs/Content/VersionHistory/v1.0.0.0.aml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Version 1.0.0.0 was released on 12/05/2014. 6 | 7 | 8 | 9 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/StringTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace Sequences.Tests 10 | { 11 | public class StringTests 12 | { 13 | [Fact] 14 | public void ToStringTest() 15 | { 16 | var seq = Sequence.Range(1, 4); 17 | Assert.Equal("Sequence(1, ?)", seq.ToString()); 18 | } 19 | 20 | [Fact] 21 | public void MkString_JoinsElements() 22 | { 23 | var seq = Sequence.Range(1, 4); 24 | Assert.Equal("123", seq.MkString()); 25 | } 26 | 27 | [Fact] 28 | public void MkString_JoinsElements_WithSeparator() 29 | { 30 | var seq = Sequence.Range(1, 4); 31 | Assert.Equal("1-2-3", seq.MkString("-")); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Sequences/EnumeratorEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Sequences 8 | { 9 | internal static class EnumeratorEx 10 | { 11 | /// 12 | /// Call MoveNext, and dispose of the iterator if MoveNext returns false or throws an exception. 13 | /// 14 | public static bool TryMoveNext(this IEnumerator iterator) 15 | { 16 | try 17 | { 18 | bool moved = iterator.MoveNext(); 19 | 20 | if (!moved) 21 | iterator.Dispose(); 22 | 23 | return moved; 24 | } 25 | catch (Exception) 26 | { 27 | if (iterator != null) 28 | iterator.Dispose(); 29 | 30 | throw; 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/Sequence.Docs/ContentLayout.content: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Functional/TriangularNumbers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests.Functional 9 | { 10 | public class TriangularNumbers 11 | { 12 | private readonly ISequence _expectedNumbers = Sequence.With(0, 1, 3, 6, 10, 15, 21, 28, 36, 45); 13 | 14 | [Fact] 15 | public void V1() 16 | { 17 | var numbers = Sequence.From(0) 18 | .Select(n => n*(n + 1)/2); 19 | 20 | Assert.Equal(_expectedNumbers, numbers.Take(10)); 21 | } 22 | 23 | [Fact] 24 | public void V2() 25 | { 26 | var numbers = Triangular(0, 0); 27 | 28 | Assert.Equal(_expectedNumbers, numbers.Take(10)); 29 | } 30 | 31 | private ISequence Triangular(int triangularNumber, int index) 32 | { 33 | return new Sequence(triangularNumber, () => Triangular(triangularNumber + index + 1, index + 1)); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/EmptySequenceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class EmptySequenceTests 11 | { 12 | [Fact] 13 | public void Sequence_HasNoTail() 14 | { 15 | Assert.Throws(() => Sequence.Empty().Tail); 16 | } 17 | 18 | [Fact] 19 | public void Sequence_HasNoHead() 20 | { 21 | Assert.Throws(() => Sequence.Empty().Head); 22 | } 23 | 24 | [Fact] 25 | public void Sequence_IsEmpty() 26 | { 27 | Assert.True(Sequence.Empty().IsEmpty); 28 | } 29 | 30 | [Fact] 31 | public void Sequence_HasNoInit() 32 | { 33 | Assert.Throws(() => Sequence.Empty().Init); 34 | } 35 | 36 | [Fact] 37 | public void ToStringTest() 38 | { 39 | Assert.Equal("Sequence()", Sequence.Empty().ToString()); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/InitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class InitTests 11 | { 12 | [Fact] 13 | public void Init_ExcludesLastElement() 14 | { 15 | var sequence = Sequence.Range(1, 6); 16 | var init = sequence.Init; 17 | int[] expectedInit = {1, 2, 3, 4}; 18 | 19 | Assert.Equal(expectedInit, init); 20 | } 21 | 22 | [Fact] 23 | public void Inits_IteratesOverInits() 24 | { 25 | var sequence = Sequence.Range(1, 4); 26 | var inits = sequence.Inits().ToList(); 27 | var expectedInits = new[] 28 | { 29 | new[] {1, 2, 3}, 30 | new[] {1, 2}, 31 | new[] {1}, 32 | new int[] {} 33 | }; 34 | 35 | Assert.Equal(expectedInits.Length, inits.Count); 36 | 37 | for (int i = 0; i < inits.Count; i++) 38 | Assert.Equal(expectedInits[i], inits[i]); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Sequences/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Resources; 2 | using System.Reflection; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | 6 | // General Information about an assembly is controlled through the following 7 | // set of attributes. Change these attribute values to modify the information 8 | // associated with an assembly. 9 | [assembly: AssemblyTitle("Sequences")] 10 | [assembly: AssemblyDescription("")] 11 | [assembly: AssemblyConfiguration("")] 12 | [assembly: AssemblyCompany("")] 13 | [assembly: AssemblyProduct("Sequences")] 14 | [assembly: AssemblyCopyright("Copyright © 2014")] 15 | [assembly: AssemblyTrademark("")] 16 | [assembly: AssemblyCulture("")] 17 | [assembly: NeutralResourcesLanguage("en")] 18 | 19 | // Version information for an assembly consists of the following four values: 20 | // 21 | // Major Version 22 | // Minor Version 23 | // Build Number 24 | // Revision 25 | // 26 | // You can specify all the values or you can default the Build and Revision Numbers 27 | // by using the '*' as shown below: 28 | // [assembly: AssemblyVersion("1.0.*")] 29 | [assembly: AssemblyVersion("1.0.1")] 30 | [assembly: AssemblyFileVersion("1.0.1")] 31 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Functional/LuckyNumbers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests.Functional 9 | { 10 | public class LuckyNumbers 11 | { 12 | private readonly ISequence _expectedLuckyNumbers = Sequence.With( 13 | 1, 3, 7, 9, 13, 15, 21, 25, 31, 33, 37, 43, 49, 51, 63, 67, 69, 73, 75, 79); 14 | 15 | [Fact] 16 | public void V1() 17 | { 18 | var luckies = Lucky(); 19 | 20 | Assert.Equal(_expectedLuckyNumbers, luckies.Take(20)); 21 | } 22 | 23 | private ISequence Lucky() 24 | { 25 | var oddInts = Sequence.Iterate(1, e => e + 2); 26 | return new Sequence(1, () => Lucky(oddInts, 1)); 27 | } 28 | 29 | private ISequence Lucky(ISequence seq, int index) 30 | { 31 | //select the next element 32 | var n = seq[index]; 33 | 34 | //remove every nth element from the sequence 35 | var filtered = seq.Sliding(n - 1, n).Flatten(); 36 | 37 | return new Sequence(n, () => Lucky(filtered, index + 1)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Sequences/EmptySequence`1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Sequences 8 | { 9 | internal class EmptySequence : Sequence 10 | { 11 | private static readonly Lazy> Empty = new Lazy>(() => new EmptySequence()); 12 | 13 | private EmptySequence() : base(default(T), null as Lazy>) 14 | { 15 | } 16 | 17 | public static ISequence Instance 18 | { 19 | get { return Empty.Value; } 20 | } 21 | 22 | public override bool IsEmpty 23 | { 24 | get { return true; } 25 | } 26 | 27 | public override T Head 28 | { 29 | get { throw new InvalidOperationException("An empty sequence doesn't have a head."); } 30 | } 31 | 32 | public override ISequence Tail 33 | { 34 | get { throw new InvalidOperationException("An empty sequence doesn't have a tail."); } 35 | } 36 | 37 | public override ISequence Init 38 | { 39 | get { throw new InvalidOperationException("Cannot call Init on an empty sequence."); } 40 | } 41 | 42 | public override string ToString() 43 | { 44 | return "Sequence()"; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /docs/Sequence.Docs/Content/VersionHistory/VersionHistory.aml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | The topics in this section describe the various changes made to Sequences over the 7 | life of the project. 8 | 9 | 10 | 11 |
12 | Version History 13 | 14 | Select a version below to see a description of its changes. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 |
43 |
44 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Functional/Fibonacci.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Sequences.Tests.Functional.Extensions; 7 | using Xunit; 8 | 9 | namespace Sequences.Tests.Functional 10 | { 11 | public class Fibonacci 12 | { 13 | private readonly int[] _expectedFibs = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 }; 14 | 15 | [Fact] 16 | public void Declarative() 17 | { 18 | ISequence fibs = null; 19 | fibs = Sequence.With(0, 1) //start with 0, 1... 20 | .Concat(() => //and then 21 | fibs.Zip(fibs.Tail) //zip the sequence with its tail (i.e., (0,1), (1,1), (1,2), (2,3), (3, 5)) 22 | .Select(TupleEx.Sum)); //select the sum of each pair (i.e., 1, 2, 3, 5, 8) 23 | 24 | Assert.Equal(_expectedFibs, fibs.Take(10)); 25 | } 26 | 27 | [Fact] 28 | public void Loop() 29 | { 30 | var fibs = Fibs(0, 1); 31 | 32 | Assert.Equal(_expectedFibs, fibs.Take(10)); 33 | } 34 | 35 | //current and next are any two consecutive fibonacci numbers. 36 | //e.g., 0 and 1, or 5 and 8 37 | private ISequence Fibs(int current, int next) 38 | { 39 | return new Sequence(current, () => Fibs(next, current + next)); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/PadToTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class PadToTests 11 | { 12 | [Fact] 13 | public void PadTo_AppendsElements() 14 | { 15 | var sequence = Sequence.Range(1, 4); 16 | var padded = sequence.PadTo(5, 0); 17 | int[] expectedPadded = {1, 2, 3, 0, 0}; 18 | 19 | Assert.Equal(expectedPadded, padded); 20 | } 21 | 22 | [Fact] 23 | public void PadTo_DoesNothing_When_LengthIsLessThanCount() 24 | { 25 | var sequence = Sequence.Range(1, 4); 26 | var padded = sequence.PadTo(2, 0); 27 | int[] expectedPadded = {1, 2, 3}; 28 | 29 | Assert.Equal(expectedPadded, padded); 30 | } 31 | 32 | [Fact] 33 | public void PadTo_DoesNothing_When_LengthIsEqualToCount() 34 | { 35 | var sequence = Sequence.Range(1, 4); 36 | var padded = sequence.PadTo(3, 0); 37 | int[] expectedPadded = {1, 2, 3}; 38 | 39 | Assert.Equal(expectedPadded, padded); 40 | } 41 | 42 | [Fact] 43 | public void PadTo_Returns_SameSequence_When_LengthIsNegative() 44 | { 45 | var sequence = Sequence.Range(1, 4); 46 | var padded = sequence.PadTo(-1, 0); 47 | 48 | Assert.Same(sequence, padded); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/Sequence.Docs/Content/VersionHistory/v1.0.1.0.aml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Version 1.0.1.0 was released on 27/05/2014. 7 | 8 | 9 | 10 | 11 |
12 | Changes in This Release 13 | 14 | 15 | 16 | 17 | 18 | Improved iterators to allow a more efficient garbage collection 19 | 20 | 21 | 22 | 23 | 24 | Added 25 | 26 | M:Sequences.ISequence`1.IndexOfSlice(System.Collections.Generic.IEnumerable{`0}) 27 | 28 | and 29 | 30 | M:Sequences.ISequence`1.ContainsSlice(System.Collections.Generic.IEnumerable{`0}) 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Sequences.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Sequences.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("ce69b025-4389-4000-bb99-4518e674ef92")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Functional/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Sequences.Tests.Functional")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Sequences.Tests.Functional")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("11e24634-c4a7-49ae-b737-b8c63f954a66")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /dist/Sequences.1.0.0.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sequences 5 | 1.0.1 6 | Sequences 7 | DCastro 8 | DCastro 9 | https://github.com/dcastro/Sequences 10 | false 11 | Sequences is a port of Scala's Stream[+A] to C#. 12 | 13 | A Sequence<T> is an immutable lazy list whose elements are only evaluated when they are needed. A sequence is composed by a head (the first element) and a lazily-evaluated tail (the remaining elements). 14 | 15 | The fact that the tail is lazily-evaluated, makes it easy to represent infinite series or sets. 16 | 17 | See the project's page for examples: https://github.com/dcastro/Sequences 18 | Sequences is a port of Scala's Streams to C#. It features lazy evaluation, immutability and memoization. 19 | https://github.com/dcastro/Sequences/releases 20 | © 2014 Diogo Castro 21 | enumerable sequence collection immutable data-structures scala lazy stream 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Functional/Factorials.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Sequences.Tests.Functional.Extensions; 7 | using Xunit; 8 | 9 | namespace Sequences.Tests.Functional 10 | { 11 | public class Factorials 12 | { 13 | private readonly ISequence _expectedFactorials = 14 | Sequence.With(1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880); 15 | 16 | [Fact] 17 | public void V1() 18 | { 19 | var naturals = Sequence.From(1); 20 | 21 | ISequence factorials = null; 22 | factorials = new Sequence(1, //start with (1) at index 0 23 | () => factorials.Zip(naturals) //zip factorials (1, ?) with naturals (1,2,3, ...) 24 | .Select(TupleEx.Product)); //select the product of each tuple (1,1) => 1, (1,2) => 2, (2,3) => 6 25 | /** 26 | * factorials[0] returns 1, eagerly evaluated. 27 | * Then, we zip factorials (1, ?) with naturals (1,2,3,...), and select the product of each tuple 28 | * 29 | * factorials[1] returns (1,?) zip (1,2,3,...) select(item1 * item2)[0] = factorials[0] * naturals[0] = 1 * 1 = 1 30 | * factorials[2] returns (1,1,?) zip (1,2,3,...) select(item1 * item2)[1] = factorials[1] * naturals[1] = 1 * 2 = 2 31 | * factorials[3] returns (1,1,2,?) zip (1,2,3,...) select(item1 * item2)[2] = factorials[2] * naturals[2] = 2 * 3 = 6 32 | */ 33 | 34 | Assert.Equal(_expectedFactorials, factorials.Take(10)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/SlicingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class SlicingTests 11 | { 12 | [Fact] 13 | public void Slice_ReturnsSubset() 14 | { 15 | var sequence = Sequence.From(0); 16 | var slice = sequence.Slice(4, 8); 17 | int[] expectedSlice = {4, 5, 6, 7}; 18 | 19 | Assert.Equal(expectedSlice, slice); 20 | } 21 | 22 | [Fact] 23 | public void Slice_StartsAtZero_When_FromIsNegative() 24 | { 25 | var sequence = Sequence.From(0); 26 | var slice = sequence.Slice(-5, 5); 27 | int[] expectedSlice = {0, 1, 2, 3, 4}; 28 | 29 | Assert.Equal(expectedSlice, slice); 30 | } 31 | 32 | [Fact] 33 | public void Slice_ReturnsEmptySequence_When_SequenceIsEmpty() 34 | { 35 | var sequence = Sequence.Empty(); 36 | var slice = sequence.Slice(0, 5); 37 | int[] expectedSlice = {}; 38 | 39 | Assert.Equal(expectedSlice, slice); 40 | } 41 | 42 | [Fact] 43 | public void Slice_ReturnsEmptySequence_When_UntilIsLowerThanFrom() 44 | { 45 | var sequence = Sequence.From(0); 46 | var slice = sequence.Slice(5, 0); 47 | int[] expectedSlice = {}; 48 | 49 | Assert.Equal(expectedSlice, slice); 50 | } 51 | 52 | [Fact] 53 | public void Slice_ReturnsShorterSlice_When_UntilIsHigherThanCount() 54 | { 55 | var sequence = Sequence.Range(0, 3, 1); 56 | var slice = sequence.Slice(1, 5); 57 | int[] expectedSlice = {1, 2}; 58 | 59 | Assert.Equal(expectedSlice, slice); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/GroupedTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class GroupedTests 11 | { 12 | [Fact] 13 | public void Grouped_GroupsElements() 14 | { 15 | var sequence = Sequence.Range(0, 6); 16 | var groups = sequence.Grouped(3).ToList(); 17 | var expectedGroups = new[] 18 | { 19 | new[] {0, 1, 2}, 20 | new[] {3, 4, 5} 21 | }; 22 | 23 | Assert.Equal(expectedGroups.Length, groups.Count); 24 | 25 | for (int i = 0; i < groups.Count; i++) 26 | Assert.Equal(expectedGroups[i], groups[i]); 27 | } 28 | 29 | [Fact] 30 | public void Grouped_TruncatesLastGroup() 31 | { 32 | 33 | var sequence = Sequence.Range(0, 5); 34 | var groups = sequence.Grouped(3).ToList(); 35 | var expectedGroups = new[] 36 | { 37 | new[] {0, 1, 2}, 38 | new[] {3, 4} 39 | }; 40 | 41 | Assert.Equal(expectedGroups.Length, groups.Count); 42 | 43 | for (int i = 0; i < groups.Count; i++) 44 | Assert.Equal(expectedGroups[i], groups[i]); 45 | } 46 | 47 | [Fact] 48 | public void Grouped_Returns_EmptyEnumerable_When_SequenceIsEmpty() 49 | { 50 | var sequence = Sequence.Empty(); 51 | var groups = sequence.Grouped(10); 52 | 53 | Assert.False(groups.Any()); 54 | } 55 | 56 | [Fact] 57 | public void Grouped_ThrowsException_When_SizeIsNotPositive() 58 | { 59 | Assert.Throws(() => Sequence.From(0).Grouped(0)); 60 | Assert.Throws(() => Sequence.From(0).Grouped(-1)); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/PatchTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class PatchTests 11 | { 12 | [Fact] 13 | public void Patch_ReplacesWithPatch() 14 | { 15 | var sequence = Sequence.Range(0, 6); 16 | var patched = sequence.Patch(2, new[] {9, 9}, 2); 17 | int[] expectedPatched = {0, 1, 9, 9, 4, 5}; 18 | 19 | Assert.Equal(expectedPatched, patched); 20 | } 21 | 22 | [Fact] 23 | public void Patch_ErasesOriginalElements() 24 | { 25 | var sequence = Sequence.Range(0, 6); 26 | var patched = sequence.Patch(2, new int[] {}, 2); 27 | int[] expectedPatched = {0, 1, 4, 5}; 28 | 29 | Assert.Equal(expectedPatched, patched); 30 | } 31 | 32 | [Fact] 33 | public void Patch_ConcatenatesPatch_When_FromIsEqualToCount() 34 | { 35 | var sequence = Sequence.Range(0, 4); 36 | var patched = sequence.Patch(4, new[] {9, 9}, 2); 37 | int[] expectedPatched = {0, 1, 2, 3, 9, 9}; 38 | 39 | Assert.Equal(expectedPatched, patched); 40 | } 41 | 42 | [Fact] 43 | public void Patch_ConcatenatesPatch_When_FromIsGreaterThanCount() 44 | { 45 | var sequence = Sequence.Range(0, 4); 46 | var patched = sequence.Patch(10, new[] {9, 9}, 2); 47 | int[] expectedPatched = {0, 1, 2, 3, 9, 9}; 48 | 49 | Assert.Equal(expectedPatched, patched); 50 | } 51 | 52 | [Fact] 53 | public void Patch_PrependsPatch_When_FromIsNegative() 54 | { 55 | var sequence = Sequence.Range(0, 4); 56 | var patched = sequence.Patch(-1, new[] {9, 9}, 2); 57 | int[] expectedPatched = {9, 9, 2, 3}; 58 | 59 | Assert.Equal(expectedPatched, patched); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/PermutationsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class PermutationsTests 11 | { 12 | [Fact] 13 | public void Permutations_RearrangesElements() 14 | { 15 | var sequence = Sequence.Range(1, 4); 16 | var permutations = sequence.Permutations().ToList(); 17 | 18 | var expectedPermutations = new[] 19 | { 20 | new[] {1, 2, 3}, 21 | new[] {1, 3, 2}, 22 | new[] {2, 1, 3}, 23 | new[] {2, 3, 1}, 24 | new[] {3, 1, 2}, 25 | new[] {3, 2, 1} 26 | }; 27 | 28 | Assert.Equal(expectedPermutations.Length, permutations.Count); 29 | 30 | for (int i = 0; i < permutations.Count; i++) 31 | Assert.Equal(expectedPermutations[i], permutations[i]); 32 | } 33 | 34 | [Fact] 35 | public void Permutations_Excludes_Duplicates() 36 | { 37 | var permutations = "abb".AsSequence().Permutations().ToList(); 38 | 39 | var expectedPermutations = new[] 40 | { 41 | new[] {'a', 'b', 'b'}, 42 | new[] {'b', 'a', 'b'}, 43 | new[] {'b', 'b', 'a'} 44 | }; 45 | 46 | Assert.Equal(expectedPermutations.Length, permutations.Count); 47 | 48 | for (int i = 0; i < permutations.Count; i++) 49 | Assert.Equal(expectedPermutations[i], permutations[i]); 50 | } 51 | 52 | [Fact] 53 | public void Permutations_Returns_OneEmptySequence_When_SequenceIsEmpty() 54 | { 55 | var sequence = Sequence.Empty(); 56 | var permutations = sequence.Permutations().ToList(); 57 | 58 | Assert.Equal(1, permutations.Count); 59 | Assert.Empty(permutations.First()); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/FlattenTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class FlattenTests 11 | { 12 | [Fact] 13 | public void Flatten_SequenceOfEnumerables_ConcatenatesEnumerables() 14 | { 15 | ISequence> sequence = 16 | Sequence.With>( 17 | new[] {1, 2, 3}, 18 | new int[] {}, 19 | new[] {4, 5}, 20 | new[] {6, 7, 8}, 21 | new int[] {} 22 | ); 23 | 24 | var flattened = sequence.Flatten(); 25 | int[] expectedFlattened = {1, 2, 3, 4, 5, 6, 7, 8}; 26 | 27 | Assert.Equal(expectedFlattened, flattened); 28 | } 29 | 30 | [Fact] 31 | public void Flatten_EnumerableOfSequences_ConcatenatesSequences() 32 | { 33 | IEnumerable> sequence = 34 | new[] 35 | { 36 | Sequence.With(1, 2, 3), 37 | Sequence.Empty(), 38 | Sequence.With(4, 5), 39 | Sequence.With(6, 7, 8), 40 | Sequence.Empty() 41 | }; 42 | 43 | var flattened = sequence.Flatten(); 44 | int[] expectedFlattened = {1, 2, 3, 4, 5, 6, 7, 8}; 45 | 46 | Assert.Equal(expectedFlattened, flattened); 47 | } 48 | 49 | [Fact] 50 | public void Flatten_SequenceOfSequences_ConcatenatesSequences() 51 | { 52 | ISequence> sequence = 53 | Sequence.With( 54 | Sequence.With(1, 2, 3), 55 | Sequence.Empty(), 56 | Sequence.With(4, 5), 57 | Sequence.With(6, 7, 8), 58 | Sequence.Empty() 59 | ); 60 | 61 | var flattened = sequence.Flatten(); 62 | int[] expectedFlattened = {1, 2, 3, 4, 5, 6, 7, 8}; 63 | 64 | Assert.Equal(expectedFlattened, flattened); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/CombinationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class CombinationTests 11 | { 12 | [Fact] 13 | public void Combinations_CombinesElements() 14 | { 15 | var sequence = Sequence.Range(1, 6); 16 | var combs = sequence.Combinations(3).ToList(); 17 | 18 | var expectedCombs = new[] 19 | { 20 | new[] {1, 2, 3}, 21 | new[] {1, 2, 4}, 22 | new[] {1, 2, 5}, 23 | new[] {1, 3, 4}, 24 | new[] {1, 3, 5}, 25 | new[] {1, 4, 5}, 26 | new[] {2, 3, 4}, 27 | new[] {2, 3, 5}, 28 | new[] {2, 4, 5}, 29 | new[] {3, 4, 5} 30 | }; 31 | 32 | Assert.Equal(expectedCombs.Length, combs.Count); 33 | 34 | for (int i = 0; i < combs.Count; i++) 35 | Assert.Equal(expectedCombs[i], combs[i]); 36 | } 37 | 38 | [Fact] 39 | public void Combinations_Excludes_Duplicates() 40 | { 41 | var combs = "abbbc".AsSequence().Combinations(2).ToList(); 42 | 43 | var expectedCombs = new[] 44 | { 45 | new[] {'a', 'b'}, 46 | new[] {'a', 'c'}, 47 | new[] {'b', 'b'}, 48 | new[] {'b', 'c'} 49 | }; 50 | 51 | Assert.Equal(expectedCombs.Length, combs.Count); 52 | 53 | for (int i = 0; i < combs.Count; i++) 54 | Assert.Equal(expectedCombs[i], combs[i]); 55 | } 56 | 57 | [Fact] 58 | public void Combinations_Returns_NoSequences_When_SequenceIsEmpty() 59 | { 60 | var sequence = Sequence.Empty(); 61 | var combs = sequence.Combinations(2); 62 | 63 | Assert.Empty(combs); 64 | } 65 | 66 | [Fact] 67 | public void Combinations_Returns_OneEmptySequence_When_SizeIsZero() 68 | { 69 | var sequence = Sequence.Range(1, 6); 70 | var combs = sequence.Combinations(0).ToList(); 71 | 72 | Assert.Equal(1, combs.Count); 73 | Assert.Empty(combs.First()); 74 | } 75 | 76 | [Fact] 77 | public void Combinations_ThrowsException_When_SizeIsNegative() 78 | { 79 | var sequence = Sequence.Range(1, 6); 80 | Assert.Throws(() => sequence.Combinations(-1)); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/LengthTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | using Xunit.Extensions; 8 | 9 | namespace Sequences.Tests 10 | { 11 | public class LengthTests 12 | { 13 | [Fact] 14 | public void Count_Returns_SequenceLength() 15 | { 16 | var sequence = Sequence.With(1, 2, 3); 17 | Assert.Equal(3, sequence.Count); 18 | } 19 | 20 | [Fact] 21 | public void HasDefiniteSize_ReturnsFalse_When_SequenceHasNotBeenFullyEvaluated() 22 | { 23 | var sequence = Sequence.Range(0, 6, 1); 24 | 25 | sequence.Take(5).Force(); 26 | 27 | Assert.False(sequence.HasDefiniteSize); 28 | } 29 | 30 | [Fact] 31 | public void HasDefiniteSize_ReturnsTrue_When_SequenceHasBeenEvaluated() 32 | { 33 | var sequence = Sequence.Range(0, 6, 1); 34 | 35 | sequence.Force(); 36 | 37 | Assert.True(sequence.HasDefiniteSize); 38 | } 39 | 40 | [Fact] 41 | public void HasDefiniteSize_DoesntEvaluateTail() 42 | { 43 | var sequence = Sequence.Range(0, 6, 1); 44 | bool hasDefiniteSize = sequence.HasDefiniteSize; 45 | 46 | Assert.False(sequence.IsTailDefined); 47 | } 48 | 49 | [Theory] 50 | [InlineData(-1, 1)] 51 | [InlineData(0, 1)] 52 | [InlineData(1, 1)] 53 | [InlineData(2, 1)] 54 | [InlineData(3, 0)] 55 | [InlineData(4, -1)] 56 | public void LengthCompare_ReturnsExpectedResult_When_CountEqualsThree(int length, int expectedResult) 57 | { 58 | var sequence = Sequence.Range(1, 4); 59 | Assert.Equal(expectedResult, sequence.LengthCompare(length)); 60 | } 61 | 62 | [Theory] 63 | [InlineData(-1, 1)] 64 | [InlineData(0, 1)] 65 | [InlineData(1, 1)] 66 | [InlineData(2, 1)] 67 | public void LengthCompare_ReturnsExpectedResult_When_SequenceIsInfinite(int length, int expectedResult) 68 | { 69 | var sequence = Sequence.From(1); 70 | Assert.Equal(expectedResult, sequence.LengthCompare(length)); 71 | } 72 | 73 | [Theory] 74 | [InlineData(-1, 1)] 75 | [InlineData(0, 0)] 76 | [InlineData(1, -1)] 77 | public void LengthCompare_ReturnsExpectedResult_When_SequenceIsEmpty(int length, int expectedResult) 78 | { 79 | var sequence = Sequence.Empty(); 80 | Assert.Equal(expectedResult, sequence.LengthCompare(length)); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/TailTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Moq; 7 | using Xunit; 8 | 9 | namespace Sequences.Tests 10 | { 11 | public class TailTests 12 | { 13 | [Fact] 14 | public void Tail_Is_LazilyEvaluated() 15 | { 16 | //Arrange 17 | var tailMock = new Mock>>(); 18 | tailMock.Setup(tail => tail()).Returns(Sequence.Empty()); 19 | 20 | var sequence = new Sequence(1, tailMock.Object); 21 | 22 | //Act & Assert 23 | tailMock.Verify(tail => tail(), Times.Never); 24 | 25 | Assert.Equal(Sequence.Empty(), sequence.Tail); 26 | tailMock.Verify(tail => tail(), Times.Once); 27 | } 28 | 29 | [Fact] 30 | public void IsTailDefined_ReturnsFalse_When_TailHasntBeenEvaluated() 31 | { 32 | var sequence = new Sequence(1, Sequence.Empty); 33 | Assert.False(sequence.IsTailDefined); 34 | } 35 | 36 | [Fact] 37 | public void IsTailDefined_ReturnsTrue_When_TailHasBeenEvaluated() 38 | { 39 | var sequence = new Sequence(1, Sequence.Empty); 40 | var tail = sequence.Tail; 41 | Assert.True(sequence.IsTailDefined); 42 | } 43 | 44 | [Fact] 45 | public void Force_EvaluatesTail() 46 | { 47 | var sequence = Sequence.Range(0, 6, 1); 48 | Assert.False(sequence.HasDefiniteSize); 49 | 50 | sequence.Force(); 51 | Assert.True(sequence.HasDefiniteSize); 52 | } 53 | 54 | [Fact] 55 | public void Tails_IteratesOverTails() 56 | { 57 | var sequence = Sequence.Range(1, 4); 58 | var tails = sequence.Tails().ToList(); 59 | var expectedTails = new[] 60 | { 61 | new[] {1, 2, 3}, 62 | new[] {2, 3}, 63 | new[] {3}, 64 | new int[] {} 65 | }; 66 | 67 | Assert.Equal(expectedTails.Length, tails.Count); 68 | 69 | for (int i = 0; i < tails.Count; i++) 70 | Assert.Equal(expectedTails[i], tails[i]); 71 | } 72 | 73 | [Fact] 74 | public void NonEmptyTails_Excludes_EmptySequence() 75 | { 76 | var sequence = Sequence.Range(1, 4); 77 | var tails = sequence.NonEmptyTails().ToList(); 78 | var expectedTails = new[] 79 | { 80 | new[] {1, 2, 3}, 81 | new[] {2, 3}, 82 | new[] {3} 83 | }; 84 | 85 | Assert.Equal(expectedTails.Length, tails.Count); 86 | 87 | for (int i = 0; i < tails.Count; i++) 88 | Assert.Equal(expectedTails[i], tails[i]); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 19 | !packages/*/build/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | 25 | *_i.c 26 | *_p.c 27 | *.ilk 28 | *.meta 29 | *.obj 30 | *.pch 31 | *.pdb 32 | *.pgc 33 | *.pgd 34 | *.rsp 35 | *.sbr 36 | *.tlb 37 | *.tli 38 | *.tlh 39 | *.tmp 40 | *.tmp_proj 41 | *.log 42 | *.vspscc 43 | *.vssscc 44 | .builds 45 | *.pidb 46 | *.log 47 | *.scc 48 | 49 | # Visual C++ cache files 50 | ipch/ 51 | *.aps 52 | *.ncb 53 | *.opensdf 54 | *.sdf 55 | *.cachefile 56 | 57 | # Visual Studio profiler 58 | *.psess 59 | *.vsp 60 | *.vspx 61 | 62 | # Guidance Automation Toolkit 63 | *.gpState 64 | 65 | # ReSharper is a .NET coding add-in 66 | _ReSharper*/ 67 | *.[Rr]e[Ss]harper 68 | 69 | # TeamCity is a build add-in 70 | _TeamCity* 71 | 72 | # DotCover is a Code Coverage Tool 73 | *.dotCover 74 | 75 | # NCrunch 76 | *.ncrunch* 77 | .*crunch*.local.xml 78 | 79 | # Installshield output folder 80 | [Ee]xpress/ 81 | 82 | # DocProject is a documentation generator add-in 83 | DocProject/buildhelp/ 84 | DocProject/Help/*.HxT 85 | DocProject/Help/*.HxC 86 | DocProject/Help/*.hhc 87 | DocProject/Help/*.hhk 88 | DocProject/Help/*.hhp 89 | DocProject/Help/Html2 90 | DocProject/Help/html 91 | 92 | # Click-Once directory 93 | publish/ 94 | 95 | # Publish Web Output 96 | *.Publish.xml 97 | *.pubxml 98 | 99 | # NuGet Packages Directory 100 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 101 | packages/ 102 | lib/ 103 | !NuGet.exe 104 | 105 | # Windows Azure Build Output 106 | csx 107 | *.build.csdef 108 | 109 | # Windows Store app package directory 110 | AppPackages/ 111 | 112 | # Others 113 | sql/ 114 | *.Cache 115 | ClientBin/ 116 | [Ss]tyle[Cc]op.* 117 | ~$* 118 | *~ 119 | *.dbmdl 120 | *.[Pp]ublish.xml 121 | *.pfx 122 | *.publishsettings 123 | 124 | # RIA/Silverlight projects 125 | Generated_Code/ 126 | 127 | # Backup & report files from converting an old project file to a newer 128 | # Visual Studio version. Backup files are not needed, because we have git ;-) 129 | _UpgradeReport_Files/ 130 | Backup*/ 131 | UpgradeLog*.XML 132 | UpgradeLog*.htm 133 | 134 | # SQL Server files 135 | App_Data/*.mdf 136 | App_Data/*.ldf 137 | 138 | # ========================= 139 | # Windows detritus 140 | # ========================= 141 | 142 | # Windows image file caches 143 | Thumbs.db 144 | ehthumbs.db 145 | 146 | # Folder config file 147 | Desktop.ini 148 | 149 | # Recycle Bin used on file shares 150 | $RECYCLE.BIN/ 151 | 152 | # Mac crap 153 | .DS_Store 154 | *.nupkg 155 | 156 | #SandCastle 157 | docs/help/* 158 | -------------------------------------------------------------------------------- /Sequences.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sequences.Tests.Unit", "tests\Sequences.Tests.Unit\Sequences.Tests.Unit.csproj", "{97B797FB-B2D8-414B-9459-71EB27113D60}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sequences", "src\Sequences\Sequences.csproj", "{6C9F9E66-E026-4966-AEB2-9B29B5F0DF85}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{C5490B70-626D-48B8-BE58-0F395820FF35}" 9 | ProjectSection(SolutionItems) = preProject 10 | .nuget\NuGet.Config = .nuget\NuGet.Config 11 | .nuget\NuGet.exe = .nuget\NuGet.exe 12 | .nuget\NuGet.targets = .nuget\NuGet.targets 13 | EndProjectSection 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sequences.Tests.Functional", "tests\Sequences.Tests.Functional\Sequences.Tests.Functional.csproj", "{C0460AD0-BE50-4A91-BBDF-BDBA5F2634C6}" 16 | EndProject 17 | Project("{7CF6DF6D-3B04-46F8-A40B-537D21BCA0B4}") = "Sequence.Docs", "docs\Sequence.Docs\Sequence.Docs.shfbproj", "{7807882D-4578-40A2-9E24-970E1E992EC5}" 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Docs|Any CPU = Docs|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {97B797FB-B2D8-414B-9459-71EB27113D60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {97B797FB-B2D8-414B-9459-71EB27113D60}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {97B797FB-B2D8-414B-9459-71EB27113D60}.Docs|Any CPU.ActiveCfg = Docs|Any CPU 29 | {97B797FB-B2D8-414B-9459-71EB27113D60}.Docs|Any CPU.Build.0 = Docs|Any CPU 30 | {97B797FB-B2D8-414B-9459-71EB27113D60}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {97B797FB-B2D8-414B-9459-71EB27113D60}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {6C9F9E66-E026-4966-AEB2-9B29B5F0DF85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {6C9F9E66-E026-4966-AEB2-9B29B5F0DF85}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {6C9F9E66-E026-4966-AEB2-9B29B5F0DF85}.Docs|Any CPU.ActiveCfg = Docs|Any CPU 35 | {6C9F9E66-E026-4966-AEB2-9B29B5F0DF85}.Docs|Any CPU.Build.0 = Docs|Any CPU 36 | {6C9F9E66-E026-4966-AEB2-9B29B5F0DF85}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {6C9F9E66-E026-4966-AEB2-9B29B5F0DF85}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {C0460AD0-BE50-4A91-BBDF-BDBA5F2634C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {C0460AD0-BE50-4A91-BBDF-BDBA5F2634C6}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {C0460AD0-BE50-4A91-BBDF-BDBA5F2634C6}.Docs|Any CPU.ActiveCfg = Docs|Any CPU 41 | {C0460AD0-BE50-4A91-BBDF-BDBA5F2634C6}.Docs|Any CPU.Build.0 = Docs|Any CPU 42 | {C0460AD0-BE50-4A91-BBDF-BDBA5F2634C6}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {C0460AD0-BE50-4A91-BBDF-BDBA5F2634C6}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {7807882D-4578-40A2-9E24-970E1E992EC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {7807882D-4578-40A2-9E24-970E1E992EC5}.Docs|Any CPU.ActiveCfg = Docs|Any CPU 46 | {7807882D-4578-40A2-9E24-970E1E992EC5}.Docs|Any CPU.Build.0 = Docs|Any CPU 47 | {7807882D-4578-40A2-9E24-970E1E992EC5}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | EndGlobal 53 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Functional/PrimeNumbers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests.Functional 9 | { 10 | public class PrimeNumbers 11 | { 12 | private readonly ISequence _expectedPrimes = Sequence.With(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 13 | 31, 37, 41, 43, 47, 53, 59, 61, 67, 71); 14 | 15 | [Fact] 16 | public void SieveOfEratosthenes() 17 | { 18 | /** 19 | * Generate integers in the range [2, Infinite) and then, for each index i: 20 | * 1) take the prime number p at position i of the input sequence 21 | * 2) remove all multiples of p (except p itself) from the input sequence 22 | */ 23 | var primes = Primes(Sequence.From(2), 0); 24 | 25 | Assert.Equal(_expectedPrimes, primes.Take(20)); 26 | } 27 | 28 | [Fact] 29 | public void SieveOfEratosthenes_Optimized() 30 | { 31 | var primes = PrimesOptimized(Sequence.From(2)); 32 | 33 | Assert.Equal(_expectedPrimes, primes.Take(20)); 34 | } 35 | 36 | [Fact] 37 | public void SieveOfEratosthenes_Optimized_WithRange() 38 | { 39 | /** 40 | * The first two versions work well for the first ~1000 prime numbers, but then we start getting StackOverflow exceptions 41 | * That's because we apply a lazily-evaluated "Where" filter at each step. 42 | * So when you go evaluate the 1000th prime number, you have to apply 999 filters. 43 | * 44 | * This version constrains the search for prime numbers to a range - here, [2,72) - and forces the evaluation of each filter 45 | * before movin onto the next step. 46 | */ 47 | 48 | const int max = 72; 49 | var primes = PrimesWithin(Sequence.Range(2, max)); 50 | 51 | Assert.Equal(_expectedPrimes, primes); 52 | } 53 | 54 | private ISequence Primes(ISequence seq, int index) 55 | { 56 | //take nth prime number 57 | var n = seq[index]; 58 | 59 | //remove multiples of n, except n itself 60 | var filtered = seq.Where(e => e % n != 0 || e == n); 61 | 62 | return new Sequence(n, () => Primes(filtered, index + 1)); 63 | } 64 | 65 | private ISequence PrimesOptimized(ISequence seq) 66 | { 67 | //take the next prime number 68 | var n = seq.Head; 69 | 70 | //skip n, and remove further multiples of n 71 | var filtered = seq.Tail.Where(e => e % n != 0); 72 | 73 | return new Sequence(n, () => PrimesOptimized(filtered)); 74 | } 75 | 76 | private ISequence PrimesWithin(ISequence range) 77 | { 78 | if (range.IsEmpty) 79 | return range; 80 | 81 | //take the next prime number 82 | var p = range.Head; 83 | 84 | //skip p, and remove further multiples of p 85 | //force the evaluation of the filtered sequence, to avoid stacking filters 86 | var filtered = range.Tail.Where(e => e % p != 0).Force(); 87 | 88 | return new Sequence(p, () => PrimesWithin(filtered)); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Sequences/KmpSearchAlgorithm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Sequences 7 | { 8 | /// 9 | /// Searches for a subsequence within a sequence using a variation of the Knuth-Morris-Pratt algorithm. 10 | /// This variation doesn't require that we know the size of "source" beforehand. 11 | /// 12 | internal static class KmpSearchAlgorithm 13 | { 14 | /// 15 | /// Searches for a subsequence within a sequence using a variation of the Knuth-Morris-Pratt algorithm. 16 | /// This variation doesn't require that we know the size of "source" beforehand. 17 | /// 18 | internal static int Search(IEnumerable source, T[] word, IEqualityComparer cmp = null) 19 | { 20 | if (cmp == null) 21 | cmp = EqualityComparer.Default; 22 | 23 | using (var iterator = source.GetEnumerator()) 24 | return Search(iterator, word, cmp); 25 | } 26 | 27 | /** 28 | * Assumes word.Length >= 2 29 | */ 30 | 31 | private static int Search(IEnumerator source, T[] word, IEqualityComparer cmp) 32 | { 33 | var table = KmpTable(word, cmp); 34 | 35 | int wordIndex = 0; 36 | int index = 0; 37 | 38 | //if the source is empty 39 | if (!source.MoveNext()) 40 | return -1; 41 | 42 | while (true) 43 | { 44 | var e = source.Current; 45 | 46 | if (cmp.Equals(e, word[wordIndex])) 47 | { 48 | if (wordIndex == word.Length - 1) 49 | return (index - word.Length + 1); 50 | 51 | wordIndex++; 52 | } 53 | else 54 | { 55 | int iTemp = wordIndex; 56 | wordIndex = table[wordIndex]; 57 | 58 | //if an ongoing match just failed, 59 | //compare "e" again, but this time against word[table[i]] 60 | if (iTemp > 0) 61 | continue; 62 | } 63 | 64 | index++; 65 | if (!source.MoveNext()) 66 | break; 67 | } 68 | return -1; 69 | } 70 | 71 | /// 72 | /// Builds the "partial match" table (or jump table). 73 | /// Table[i] represents how characters of "word" can be skipped, when the comparison between "e" and word[i] fails 74 | /// 75 | private static int[] KmpTable(T[] word, IEqualityComparer cmp) 76 | { 77 | var t = new int[word.Length]; 78 | 79 | t[0] = 0; 80 | t[1] = 0; 81 | 82 | int pos = 2; 83 | int cnd = 0; 84 | 85 | while (pos < word.Length) 86 | { 87 | if (cmp.Equals(word[pos - 1], word[cnd])) 88 | { 89 | t[pos] = cnd + 1; 90 | cnd++; 91 | pos++; 92 | } 93 | else if (cnd > 0) 94 | { 95 | cnd = t[cnd]; 96 | } 97 | else 98 | { 99 | t[pos] = 0; 100 | pos++; 101 | } 102 | } 103 | 104 | return t; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/CopyToTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class CopyToTests 11 | { 12 | [Fact] 13 | public void CopyTo_CopiesWholeSequence() 14 | { 15 | var sequence = Sequence.Range(1, 6); 16 | var copy = new int[5]; 17 | int[] expectedCopy = {1, 2, 3, 4, 5}; 18 | 19 | int copied = sequence.CopyTo(copy); 20 | 21 | Assert.Equal(5, copied); 22 | Assert.Equal(expectedCopy, copy); 23 | } 24 | 25 | [Fact] 26 | public void CopyTo_StopsWhenAllElementsHaveBeenCopied() 27 | { 28 | var sequence = Sequence.Range(1, 4); 29 | var copy = new int[5]; 30 | int[] expectedCopy = {1, 2, 3, 0, 0}; 31 | 32 | int copied = sequence.CopyTo(copy); 33 | 34 | Assert.Equal(3, copied); 35 | Assert.Equal(expectedCopy, copy); 36 | } 37 | 38 | [Fact] 39 | public void CopyTo_StopsWhenArrayIsFull() 40 | { 41 | var sequence = Sequence.Range(1, 10); 42 | var copy = new int[5]; 43 | int[] expectedCopy = {1, 2, 3, 4, 5}; 44 | 45 | int copied = sequence.CopyTo(copy); 46 | 47 | Assert.Equal(5, copied); 48 | Assert.Equal(expectedCopy, copy); 49 | } 50 | 51 | [Fact] 52 | public void CopyTo_StopsWhenNElementsHaveBeenCopied() 53 | { 54 | var sequence = Sequence.Range(1, 10); 55 | var copy = new int[5]; 56 | int[] expectedCopy = {1, 2, 3, 0, 0}; 57 | 58 | int copied = sequence.CopyTo(0, copy, 0, 3); 59 | 60 | Assert.Equal(3, copied); 61 | Assert.Equal(expectedCopy, copy); 62 | } 63 | 64 | [Fact] 65 | public void CopyTo_SkipsOriginOffset() 66 | { 67 | var sequence = Sequence.Range(1, 10); 68 | var copy = new int[5]; 69 | int[] expectedCopy = {3, 4, 5, 0, 0}; 70 | 71 | int copied = sequence.CopyTo(2, copy, 0, 3); 72 | 73 | Assert.Equal(3, copied); 74 | Assert.Equal(expectedCopy, copy); 75 | } 76 | 77 | [Fact] 78 | public void CopyTo_SkipsDestinationOffset() 79 | { 80 | var sequence = Sequence.Range(1, 10); 81 | var copy = new int[5]; 82 | int[] expectedCopy = {0, 0, 1, 2, 3}; 83 | 84 | int copied = sequence.CopyTo(0, copy, 2, 10); 85 | 86 | Assert.Equal(3, copied); 87 | Assert.Equal(expectedCopy, copy); 88 | } 89 | 90 | [Fact] 91 | public void CopyTo_ThrowsException_When_DestinationOffsetIsNegative() 92 | { 93 | var sequence = Sequence.Range(1, 6); 94 | var copy = new int[5]; 95 | 96 | Assert.Throws(() => sequence.CopyTo(copy, -1)); 97 | } 98 | 99 | [Fact] 100 | public void CopyTo_ThrowsException_When_OriginOffsetIsNegative() 101 | { 102 | var sequence = Sequence.Range(1, 6); 103 | var copy = new int[5]; 104 | 105 | Assert.Throws(() => sequence.CopyTo(-1, copy, 0, 1)); 106 | } 107 | 108 | [Fact] 109 | public void CopyTo_ThrowsException_When_CountIsNegative() 110 | { 111 | var sequence = Sequence.Range(1, 6); 112 | var copy = new int[5]; 113 | 114 | Assert.Throws(() => sequence.CopyTo(0, copy, 0, -1)); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/ZipTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class ZipTests 11 | { 12 | [Fact] 13 | public void Zip_AssociatesElements() 14 | { 15 | var sequence = Sequence.From(0); 16 | var zipped = sequence.Zip(sequence.Tail).Take(5); 17 | Tuple[] expectedZipped = 18 | { 19 | Tuple.Create(0, 1), 20 | Tuple.Create(1, 2), 21 | Tuple.Create(2, 3), 22 | Tuple.Create(3, 4), 23 | Tuple.Create(4, 5), 24 | }; 25 | 26 | Assert.Equal(expectedZipped, zipped); 27 | } 28 | 29 | [Fact] 30 | public void Zip_TruncatesSecondSequence_If_LongerThanFirst() 31 | { 32 | var zipped = Sequence.Range(1, 4).Zip( 33 | Sequence.Range(10, 21)); 34 | 35 | Tuple[] expectedZipped = 36 | { 37 | Tuple.Create(1, 10), 38 | Tuple.Create(2, 11), 39 | Tuple.Create(3, 12) 40 | }; 41 | 42 | Assert.Equal(expectedZipped, zipped); 43 | } 44 | 45 | 46 | [Fact] 47 | public void Zip_TruncatesFirstSequence_If_LongerThanSecond() 48 | { 49 | var zipped = Sequence.Range(10, 21).Zip( 50 | Sequence.Range(1, 4)); 51 | 52 | Tuple[] expectedZipped = 53 | { 54 | Tuple.Create(10, 1), 55 | Tuple.Create(11, 2), 56 | Tuple.Create(12, 3) 57 | }; 58 | 59 | Assert.Equal(expectedZipped, zipped); 60 | } 61 | 62 | [Fact] 63 | public void ZipAll_ExtendsFirstSequence_If_ShorterThanSecond() 64 | { 65 | var zipped = Sequence.Range(1, 4).ZipAll( 66 | Sequence.Range(1, 6), 0, 9); 67 | 68 | Tuple[] expectedZipped = 69 | { 70 | Tuple.Create(1, 1), 71 | Tuple.Create(2, 2), 72 | Tuple.Create(3, 3), 73 | Tuple.Create(0, 4), 74 | Tuple.Create(0, 5), 75 | }; 76 | 77 | Assert.Equal(expectedZipped, zipped); 78 | } 79 | 80 | [Fact] 81 | public void ZipAll_ExtendsSecondSequence_If_ShorterThanFirst() 82 | { 83 | var zipped = Sequence.Range(1, 6).ZipAll( 84 | Sequence.Range(1, 4), 0, 9); 85 | 86 | Tuple[] expectedZipped = 87 | { 88 | Tuple.Create(1, 1), 89 | Tuple.Create(2, 2), 90 | Tuple.Create(3, 3), 91 | Tuple.Create(4, 9), 92 | Tuple.Create(5, 9), 93 | }; 94 | 95 | Assert.Equal(expectedZipped, zipped); 96 | } 97 | 98 | [Fact] 99 | public void ZipWithIndex_AssociatesElementsWithTheirIndex() 100 | { 101 | var sequence = Sequence.Range(10, 16); 102 | var zipped = sequence.ZipWithIndex(); 103 | 104 | Tuple[] expectedZipped = 105 | { 106 | Tuple.Create(10, 0), 107 | Tuple.Create(11, 1), 108 | Tuple.Create(12, 2), 109 | Tuple.Create(13, 3), 110 | Tuple.Create(14, 4), 111 | Tuple.Create(15, 5), 112 | }; 113 | 114 | Assert.Equal(expectedZipped, zipped); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/SlidingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace Sequences.Tests 10 | { 11 | public class SlidingTests 12 | { 13 | [Fact] 14 | public void Sliding_Returns_SlidingWindows() 15 | { 16 | var sequence = Sequence.Range(0, 5); 17 | var windows = sequence.Sliding(3).ToList(); 18 | var expectedWindows = new List> 19 | { 20 | new List {0, 1, 2}, 21 | new List {1, 2, 3}, 22 | new List {2, 3, 4} 23 | }; 24 | 25 | Assert.Equal(expectedWindows.Count, windows.Count); 26 | 27 | for (int i = 0; i < windows.Count; i++) 28 | Assert.Equal(expectedWindows[i], windows[i]); 29 | } 30 | 31 | [Fact] 32 | public void Sliding_WithStep_TruncatesLastWindow() 33 | { 34 | var sequence = Sequence.Range(0, 6); 35 | var windows = sequence.Sliding(3, 2).ToList(); 36 | var expectedWindows = new List> 37 | { 38 | new List {0, 1, 2}, 39 | new List {2, 3, 4}, 40 | new List {4, 5} 41 | }; 42 | 43 | Assert.Equal(expectedWindows.Count, windows.Count); 44 | 45 | for (int i = 0; i < windows.Count; i++) 46 | Assert.Equal(expectedWindows[i], windows[i]); 47 | } 48 | 49 | [Fact] 50 | public void Sliding_Stops_When_LastElementIsSkipped() 51 | { 52 | var sequence = Sequence.Range(0, 8); 53 | var windows = sequence.Sliding(2, 4).ToList(); 54 | var expectedWindows = new List> 55 | { 56 | new List {0, 1}, 57 | new List {4, 5} 58 | }; 59 | 60 | Assert.Equal(expectedWindows.Count, windows.Count); 61 | 62 | for (int i = 0; i < windows.Count; i++) 63 | Assert.Equal(expectedWindows[i], windows[i]); 64 | } 65 | 66 | [Fact] 67 | public void Sliding_Returns_WholeSequence_When_SizeIsHigherThanCount() 68 | { 69 | var sequence = Sequence.Range(0, 4); 70 | var windows = sequence.Sliding(10).ToList(); 71 | var expectedWindow = new List {0, 1, 2, 3}; 72 | 73 | Assert.Equal(1, windows.Count); 74 | Assert.Equal(expectedWindow, windows.Single()); 75 | } 76 | 77 | [Fact] 78 | public void Sliding_Returns_EmptyEnumerable_When_SequenceIsEmpty() 79 | { 80 | var sequence = Sequence.Empty(); 81 | var windows = sequence.Sliding(10); 82 | 83 | Assert.False(windows.Any()); 84 | } 85 | 86 | [Fact] 87 | public void Sliding_ThrowsException_When_SizeIsNotPositive() 88 | { 89 | Assert.Throws(() => Sequence.Empty().Sliding(0)); 90 | Assert.Throws(() => Sequence.Empty().Sliding(-1)); 91 | Assert.Throws(() => Sequence.Empty().Sliding(0, 1)); 92 | Assert.Throws(() => Sequence.Empty().Sliding(-1, 1)); 93 | } 94 | 95 | [Fact] 96 | public void Sliding_ThrowsException_When_StepIsNotPositive() 97 | { 98 | Assert.Throws(() => Sequence.Empty().Sliding(1, 0)); 99 | Assert.Throws(() => Sequence.Empty().Sliding(1, -1)); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Sequences/Sequences.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11.0 6 | Debug 7 | AnyCPU 8 | {6C9F9E66-E026-4966-AEB2-9B29B5F0DF85} 9 | Library 10 | Properties 11 | Sequences 12 | Sequences 13 | v4.5 14 | Profile111 15 | 512 16 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | bin\Debug\Sequences.XML 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | bin\Release\Sequences.XML 36 | 37 | 38 | bin\Docs\ 39 | TRACE 40 | bin\Release\Sequences.XML 41 | true 42 | pdbonly 43 | AnyCPU 44 | prompt 45 | MinimumRecommendedRules.ruleset 46 | 47 | 48 | true 49 | 50 | 51 | ../../signing.snk 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | xcopy /Q /Y /E "$(TargetDir)*.*" "$(SolutionDir)bin\" 71 | 72 | 79 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/AddRemoveTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class AddRemoveTests 11 | { 12 | [Fact] 13 | public void Concat_ConcatenatesTwoSequences() 14 | { 15 | var first = Sequence.With(1, 2); 16 | var second = Sequence.With(3, 4); 17 | 18 | Assert.Equal(new[] {1, 2, 3, 4}, 19 | first.Concat(() => second)); 20 | } 21 | 22 | [Fact] 23 | public void Concat_ConcatenatesEmpty_WithNonEmpty() 24 | { 25 | var first = Sequence.Empty(); 26 | var second = Sequence.With(1, 2, 3, 4); 27 | 28 | Assert.Equal(new[] {1, 2, 3, 4}, 29 | first.Concat(() => second)); 30 | } 31 | 32 | [Fact] 33 | public void Concat_ConcatenatesNonEmpty_WithEmpty() 34 | { 35 | var first = Sequence.With(1, 2, 3, 4); 36 | var second = Sequence.Empty(); 37 | 38 | Assert.Equal(new[] {1, 2, 3, 4}, 39 | first.Concat(() => second)); 40 | } 41 | 42 | [Fact] 43 | public void Append_AppendsElementToSequence() 44 | { 45 | Assert.Equal(new[] {1, 2, 3, 4}, 46 | Sequence.With(1, 2, 3).Append(4)); 47 | } 48 | 49 | [Fact] 50 | public void Prepend_PrependsElementToSequence() 51 | { 52 | Assert.Equal(new[] {1, 2, 3, 4}, 53 | Sequence.With(2, 3, 4).Prepend(1)); 54 | } 55 | 56 | [Fact] 57 | public void Remove_RemovesElement() 58 | { 59 | var sequence = Sequence.Range(1, 6); 60 | var result = sequence.Remove(3); 61 | var expectedResult = new[] {1, 2, 4, 5}; 62 | 63 | Assert.Equal(expectedResult, result); 64 | } 65 | 66 | [Fact] 67 | public void Remove_RemovesHead() 68 | { 69 | var sequence = Sequence.Range(1, 6); 70 | var result = sequence.Remove(1); 71 | var expectedResult = new[] {2, 3, 4, 5}; 72 | 73 | Assert.Equal(expectedResult, result); 74 | } 75 | 76 | [Fact] 77 | public void Remove_Returns_EquivalentSequence_When_ElementIsNotFound() 78 | { 79 | var sequence = Sequence.Range(1, 6); 80 | var result = sequence.Remove(10); 81 | var expectedResult = new[] {1, 2, 3, 4, 5}; 82 | 83 | Assert.Equal(expectedResult, result); 84 | } 85 | 86 | [Fact] 87 | public void Remove_Returns_SameSequence_When_SequenceIsEmpty() 88 | { 89 | var sequence = Sequence.Empty(); 90 | var result = sequence.Remove(10); 91 | 92 | Assert.Same(sequence, result); 93 | } 94 | 95 | [Fact] 96 | public void Updated_UpdatesElement() 97 | { 98 | var sequence = Sequence.Range(1, 6); 99 | var updated = sequence.Updated(2, 10); 100 | int[] expectedUpdated = {1, 2, 10, 4, 5}; 101 | 102 | Assert.Equal(expectedUpdated, updated); 103 | } 104 | 105 | [Fact] 106 | public void Updated_DoesNothing_When_IndexGreaterThanCount() 107 | { 108 | var sequence = Sequence.Range(1, 6); 109 | var updated = sequence.Updated(5, 10); 110 | int[] expectedUpdated = {1, 2, 3, 4, 5}; 111 | 112 | Assert.Equal(expectedUpdated, updated); 113 | } 114 | 115 | [Fact] 116 | public void Updated_ThrowsException_When_IndexLessThanZero() 117 | { 118 | var sequence = Sequence.Range(1, 6); 119 | Assert.Throws(() => sequence.Updated(-1, 10)); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/KmpSearchTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace Sequences.Tests 9 | { 10 | public class KmpSearchTests 11 | { 12 | private const int SourcesCount = 10000; 13 | private const int SourceLength = 1000; 14 | 15 | private const int WordMinLength = 2; 16 | private const int WordMaxLength = 10; 17 | 18 | private const string Chars = "0123456789"; 19 | private readonly Random _rnd = new Random(); 20 | 21 | [Fact] 22 | public void Kmp_FindsFirstIndexOfWord() 23 | { 24 | foreach (var data in GetTestData()) 25 | { 26 | //with unknown size 27 | int strSearchResult = data.Source.IndexOf(data.Word, data.From, StringComparison.InvariantCulture); 28 | int seqSearchResult = data.Source.AsSequence().IndexOfSlice(data.Word, data.From); 29 | 30 | Assert.Equal(strSearchResult, seqSearchResult); 31 | 32 | //with known size 33 | int forcedSeqSearchResult = data.Source.AsSequence().Force().IndexOfSlice(data.Word, data.From); 34 | Assert.Equal(strSearchResult, forcedSeqSearchResult); 35 | } 36 | } 37 | 38 | [Fact] 39 | public void EdgeCases() 40 | { 41 | //source is empty + word is empty 42 | Assert.Equal(0, Sequence.Empty().IndexOfSlice(new int[] {})); 43 | Assert.Equal(0, Sequence.Empty().IndexOfSlice(new int[] {}, -1)); 44 | Assert.Equal(-1, Sequence.Empty().IndexOfSlice(new int[] {}, 1)); 45 | 46 | //word is empty 47 | Assert.Equal(0, Sequence.With(1, 2).IndexOfSlice(new int[] {})); 48 | Assert.Equal(0, Sequence.With(1, 2).IndexOfSlice(new int[] {}, -1)); 49 | Assert.Equal(1, Sequence.With(1, 2).IndexOfSlice(new int[] {}, 1)); 50 | Assert.Equal(-1, Sequence.With(1, 2).IndexOfSlice(new int[] {}, 2)); 51 | 52 | //source == word 53 | Assert.Equal(0, Sequence.With(1, 2, 3).IndexOfSlice(new[] {1, 2, 3})); 54 | Assert.Equal(0, Sequence.With(1, 2, 3).IndexOfSlice(new[] {1, 2, 3}, -1)); 55 | Assert.Equal(-1, Sequence.With(1, 2, 3).IndexOfSlice(new[] {1, 2, 3}, 1)); 56 | 57 | Assert.Equal(0, Sequence.With(1, 2, 3).Force().IndexOfSlice(new[] {1, 2, 3})); 58 | Assert.Equal(0, Sequence.With(1, 2, 3).Force().IndexOfSlice(new[] {1, 2, 3}, -1)); 59 | Assert.Equal(-1, Sequence.With(1, 2, 3).Force().IndexOfSlice(new[] {1, 2, 3}, 1)); 60 | 61 | //word.Length == 1 62 | Assert.Equal(1, Sequence.With(1, 2).Force().IndexOfSlice(new[] {2})); 63 | Assert.Equal(1, Sequence.With(1, 2).Force().IndexOfSlice(new[] {2}, -1)); 64 | Assert.Equal(0, Sequence.With(1, 2).Force().IndexOfSlice(new[] {2}, 1)); 65 | Assert.Equal(-1, Sequence.With(1, 2).Force().IndexOfSlice(new[] {2}, 2)); 66 | } 67 | 68 | private IEnumerable GetTestData() 69 | { 70 | for (int i = 0; i < SourcesCount; i++) 71 | { 72 | char[] sourceChars = Enumerable.Repeat(Chars, SourceLength) 73 | .Select(chars => chars[_rnd.Next(chars.Length)]) 74 | .ToArray(); 75 | 76 | int length = _rnd.Next(WordMinLength, WordMaxLength); 77 | int start = _rnd.Next(sourceChars.Length - length); 78 | 79 | var source = new string(sourceChars); 80 | var word = source.Substring(start, length); 81 | 82 | var from = _rnd.Next(0, start + 1); 83 | 84 | yield return new TestData 85 | { 86 | Source = source, 87 | Word = word, 88 | From = from 89 | }; 90 | } 91 | } 92 | 93 | private class TestData 94 | { 95 | public string Source; 96 | public string Word; 97 | public int From; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /docs/Sequence.Docs/Content/Welcome.aml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sequences is a port of Scala's 6 | Stream[+A] 7 | http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.Stream 8 | to C#. 9 | 10 | 11 | Sequences is available on 12 | 13 | NuGet 14 | https://www.nuget.org/packages/Sequences 15 | 16 | and the source code is on 17 | 18 | GitHub 19 | https://github.com/dcastro/Sequences 20 | 21 | 22 | 23 | 24 | 25 |
26 | Intro 27 | 28 | 29 | 30 | A T:Sequences.Sequence`1 is an immutable lazy list whose elements are only evaluated when they are needed. 31 | A sequence is composed by a head (the first element) and a lazily-evaluated tail (the remaining elements). 32 | 33 | 34 | The fact that the tail is lazily-evaluated, makes it easy to represent infinite series or sets. For example, here's how to represent the set of all natural numbers. 35 | 36 | 37 | 38 | Naturals(int start) 40 | { 41 | return new Sequence( head: start, 42 | tail: () => Naturals(start + 1)); 43 | } 44 | 45 | var naturals = Naturals(1); 46 | 47 | //take the first 5 natural numbers 48 | naturals.Take(5).ForEach(Console.Write); //prints 12345]]> 49 | 50 | 51 | 52 | Or, even simpler: 53 | 54 | 55 | 56 | 58 | 59 | 60 | 61 | Sequences also features memoization, i.e., the sequence stores previously computed values to avoid re-evaluation. 62 | 63 | 64 | 65 | 68 | { 69 | Console.WriteLine("Adding " + odd + " + 2"); 70 | return odd + 2; 71 | }); 72 | 73 | odds.Take(3).ForEach(Console.WriteLine); 74 | odds.Take(5).ForEach(Console.WriteLine); 75 | 76 | //prints 77 | //1 78 | //Adding 1 + 2 79 | //3 80 | //Adding 3 + 2 81 | //5 82 | 83 | //and then 84 | //1 85 | //3 86 | //5 87 | //Adding 5 + 2 88 | //7 89 | //Adding 7 + 2 90 | //9]]> 91 | 92 | 93 | 94 | You can iterate through an infinite sequence for as long as you want. 95 | As long as you don't hold onto its head, each sequence will be elected for garbage collection as soon as you move to the next value. 96 | This prevents an infinite sequence from occupying a large and growing ammount of memory. 97 | 98 | 99 | 100 | odd + 2)) 102 | { 103 | //when you move to Sequence(11, ?), 104 | //the previous Sequence(9, ?) is elected for collection. 105 | }]]> 106 | 107 | 108 | 109 |
110 | 111 | 112 | 113 | 114 | 115 | T:Sequences.ISequence`1 116 | T:Sequences.Sequence 117 | 118 |
119 |
120 | -------------------------------------------------------------------------------- /src/Sequences/SequenceBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Sequences 7 | { 8 | /// 9 | /// A mutable builder, capable of building sequences from elements and other collections. 10 | /// 11 | /// The type of the elements in the resulting sequences. 12 | public class SequenceBuilder 13 | { 14 | private readonly List>> _parts = new List>>(); 15 | 16 | /// 17 | /// Appends one or more elements to this builder. 18 | /// 19 | /// One or more elements to be added to this builder. 20 | /// This builder. 21 | public SequenceBuilder Append(params T[] elems) 22 | { 23 | return Append(elems.AsEnumerable()); 24 | } 25 | 26 | /// 27 | /// Appends a collection to this builder. 28 | /// 29 | /// The collection to be added to this builder. 30 | /// This builder. 31 | public SequenceBuilder Append(IEnumerable enumerable) 32 | { 33 | if (enumerable == null) throw new ArgumentNullException("enumerable"); 34 | 35 | return Append(() => enumerable); 36 | } 37 | 38 | /// 39 | /// Appends a lazily-evaluated collection to this builder. 40 | /// 41 | /// The collection to be added to this builder. 42 | /// This builder. 43 | public SequenceBuilder Append(Func> enumerable) 44 | { 45 | if (enumerable == null) throw new ArgumentNullException("enumerable"); 46 | 47 | _parts.Add(enumerable); 48 | return this; 49 | } 50 | 51 | /// 52 | /// Appends a single element to this builder. 53 | /// 54 | /// The element to be added to this builder. 55 | /// This builder. 56 | public SequenceBuilder Append(T elem) 57 | { 58 | return Append(() => elem); 59 | } 60 | 61 | /// 62 | /// Appends a single lazily-evaluated element to this builder. 63 | /// 64 | /// The element to be added to this builder. 65 | /// This builder. 66 | public SequenceBuilder Append(Func elem) 67 | { 68 | _parts.Add(() => Sequence.With(elem())); 69 | return this; 70 | } 71 | 72 | /// 73 | /// Produces a sequence from the added elements. 74 | /// 75 | /// A sequence containing the elements added to this builder. 76 | public ISequence ToSequence() 77 | { 78 | //realize sublist and flatten it as a sequence 79 | return _parts 80 | .Take(_parts.Count) 81 | .ToList() 82 | .SelectMany(enumerable => enumerable()) 83 | .AsSequence(); 84 | } 85 | 86 | /// 87 | /// Clears the contents of this builder. 88 | /// 89 | /// This builder. 90 | public SequenceBuilder Clear() 91 | { 92 | _parts.Clear(); 93 | return this; 94 | } 95 | 96 | /// 97 | /// Appends a single element to the builder. 98 | /// 99 | /// The builder to which will be added. 100 | /// The element to be added to the builder. 101 | /// The given builder. 102 | public static SequenceBuilder operator +(SequenceBuilder builder, T elem) 103 | { 104 | if (builder == null) 105 | builder = new SequenceBuilder(); 106 | 107 | return builder.Append(elem); 108 | } 109 | 110 | /// 111 | /// Appends a collection to the builder. 112 | /// 113 | /// The builder to which will be added. 114 | /// The collection to be added to the builder. 115 | /// The given builder. 116 | public static SequenceBuilder operator +(SequenceBuilder builder, IEnumerable enumerable) 117 | { 118 | if (builder == null) 119 | builder = new SequenceBuilder(); 120 | 121 | return builder.Append(enumerable); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/AggregationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace Sequences.Tests 10 | { 11 | public class AggregationTests 12 | { 13 | [Fact] 14 | public void Fold_AccumulatesValues() 15 | { 16 | int sum = Sequence.With(1, 2, 3, 4).Fold(0, (a, b) => a + b); 17 | Assert.Equal(10, sum); 18 | } 19 | 20 | [Fact] 21 | public void Fold_ReturnsSeed_When_SequenceIsEmpty() 22 | { 23 | int sum = Sequence.Empty().Fold(0, (a, b) => a + b); 24 | Assert.Equal(0, sum); 25 | } 26 | 27 | [Fact] 28 | public void Fold_GoesLeftToRight() 29 | { 30 | var additions = new List>(); 31 | var expectedAdditions = new List> 32 | { 33 | Tuple.Create(0, 1), 34 | Tuple.Create(1, 2), 35 | Tuple.Create(3, 3), 36 | Tuple.Create(6, 4) 37 | }; 38 | 39 | Sequence.With(1, 2, 3, 4).Fold(0, (a, b) => 40 | { 41 | additions.Add(Tuple.Create(a, b)); 42 | return a + b; 43 | }); 44 | 45 | Assert.Equal(expectedAdditions, additions); 46 | } 47 | 48 | [Fact] 49 | public void FoldRight_AccumulatesValues() 50 | { 51 | int sum = Sequence.With(1, 2, 3, 4).FoldRight(0, (a, b) => a + b); 52 | Assert.Equal(10, sum); 53 | } 54 | 55 | [Fact] 56 | public void FoldRight_ReturnsSeed_When_SequenceIsEmpty() 57 | { 58 | int sum = Sequence.Empty().FoldRight(0, (a, b) => a + b); 59 | Assert.Equal(0, sum); 60 | } 61 | 62 | [Fact] 63 | public void FoldRight_GoesRightToLeft() 64 | { 65 | 66 | var additions = new List>(); 67 | var expectedAdditions = new List> 68 | { 69 | Tuple.Create(4, 0), 70 | Tuple.Create(3, 4), 71 | Tuple.Create(2, 7), 72 | Tuple.Create(1, 9) 73 | }; 74 | 75 | Sequence.With(1, 2, 3, 4).FoldRight(0, (a, b) => 76 | { 77 | additions.Add(Tuple.Create(a, b)); 78 | return a + b; 79 | }); 80 | 81 | Assert.Equal(expectedAdditions, additions); 82 | } 83 | 84 | [Fact] 85 | public void Reduce_AccumulatesValues() 86 | { 87 | int sum = Sequence.With(1, 2, 3, 4).Reduce((a, b) => a + b); 88 | Assert.Equal(10, sum); 89 | } 90 | 91 | [Fact] 92 | public void Reduce_Throws_When_SequenceIsEmpty() 93 | { 94 | Assert.Throws(() => Sequence.Empty().Reduce((a, b) => a + b)); 95 | } 96 | 97 | [Fact] 98 | public void ReduceRight_AccumulatesValues() 99 | { 100 | int sum = Sequence.With(1, 2, 3, 4).ReduceRight((a, b) => a + b); 101 | Assert.Equal(10, sum); 102 | } 103 | 104 | [Fact] 105 | public void ReduceRight_Throws_When_SequenceIsEmpty() 106 | { 107 | Assert.Throws(() => Sequence.Empty().ReduceRight((a, b) => a + b)); 108 | } 109 | 110 | [Fact] 111 | public void Scan_Returns_ListOfAccumulations() 112 | { 113 | var sequence = Sequence.With(1, 2, 3, 4); 114 | var scan = sequence.Scan(0, (a, b) => a + b); 115 | var expected = new[] {0, 1, 3, 6, 10}; 116 | 117 | Assert.Equal(expected, scan); 118 | } 119 | 120 | [Fact] 121 | public void Scan_Returns_Seed_When_SequenceIsEmpty() 122 | { 123 | var sequence = Sequence.Empty(); 124 | var scan = sequence.Scan(0, (a, b) => a + b); 125 | var expected = new[] {0}; 126 | 127 | Assert.Equal(expected, scan); 128 | } 129 | 130 | [Fact] 131 | public void ScanRight_Returns_ListOfAccumulations() 132 | { 133 | var sequence = Sequence.With(1, 2, 3, 4); 134 | var scan = sequence.ScanRight(0, (a, b) => a + b); 135 | var expected = new[] {10, 9, 7, 4, 0}; 136 | 137 | Assert.Equal(expected, scan); 138 | } 139 | 140 | [Fact] 141 | public void ScanRight_Returns_Seed_When_SequenceIsEmpty() 142 | { 143 | var sequence = Sequence.Empty(); 144 | var scan = sequence.ScanRight(0, (a, b) => a + b); 145 | var expected = new[] {0}; 146 | 147 | Assert.Equal(expected, scan); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Functional/PascalsTriangle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Sequences.Tests.Functional.Extensions; 7 | using Xunit; 8 | 9 | namespace Sequences.Tests.Functional 10 | { 11 | public class PascalsTriangle 12 | { 13 | private readonly ISequence> _expectedTriangle; 14 | 15 | public PascalsTriangle() 16 | { 17 | _expectedTriangle = Sequence.With(Sequence.With(1), 18 | Sequence.With(1, 1), 19 | Sequence.With(1, 2, 1), 20 | Sequence.With(1, 3, 3, 1), 21 | Sequence.With(1, 4, 6, 4, 1), 22 | Sequence.With(1, 5, 10, 10, 5, 1)); 23 | } 24 | 25 | [Fact] 26 | public void V1() 27 | { 28 | Func, ISequence> func = //to build a row.. 29 | row => row.Zip(row.Tail) //zip the previous row with its tail, i.e., (1,3,3,1) becomes ((1,3), (3,3), (3,1)) 30 | .Select(TupleEx.Sum) //select the sum of each pair, i.e., (4, 6, 4) 31 | .Append(1) //add (1) to each end 32 | .Prepend(1); 33 | 34 | /* 35 | * Alternative syntax 36 | * 37 | Func, ISequence> func = //to build a row.. 38 | row => 39 | Sequence.With(1) //start with 1... 40 | .Concat(() => //followed by... 41 | row.Zip(row.Tail) //zipping the previous row with its tail, i.e., (1,3,3,1) becomes ((1,3), (3,3), (3,1)) 42 | .Select(TupleEx.Sum)) //and select the sum of each pair, i.e., (4, 6, 4) 43 | .Append(1); //and, finally, another 1. 44 | */ 45 | 46 | 47 | var triangle = Sequence.Iterate( 48 | Sequence.With(1), func); 49 | 50 | var sixRows = triangle.Take(6); 51 | 52 | //Assertions 53 | Assert.Equal(6, sixRows.Count); 54 | 55 | _expectedTriangle.Zip(sixRows).ForEach( 56 | rows => Assert.Equal(rows.Item1, rows.Item2)); 57 | } 58 | 59 | [Fact] 60 | public void V2() 61 | { 62 | var triangle = Pascal(); 63 | var sixRows = triangle.Take(6); 64 | 65 | //Assertions 66 | Assert.Equal(6, sixRows.Count); 67 | 68 | _expectedTriangle.Zip(sixRows).ForEach( 69 | rows => Assert.Equal(rows.Item1, rows.Item2)); 70 | } 71 | 72 | private ISequence> Pascal() 73 | { 74 | ISequence firstRow = Sequence.With(1); 75 | ISequence secondRow = Sequence.With(1, 1); 76 | 77 | return Sequence.With( 78 | firstRow, 79 | secondRow 80 | ).Concat(() => PascalAux(secondRow)); 81 | } 82 | 83 | private ISequence> PascalAux(ISequence previousRow) 84 | { 85 | ISequence newRow = previousRow 86 | .Sliding(2) //for each 2 consecutive elements... 87 | .Select(pair => pair.Sum()) //select their sum 88 | .AsSequence() 89 | .Append(1) //append (1) 90 | .Prepend(1); //prepend (1) 91 | return new Sequence>(newRow, () => PascalAux(newRow)); 92 | } 93 | 94 | [Fact] 95 | public void V3() 96 | { 97 | //similar to V2, but doesn't define auxiliary methods 98 | Func, ISequence> func = 99 | row => row.Sliding(2) 100 | .Where(group => group.LengthCompare(2) == 0) 101 | .Select(pair => pair.Sum()) 102 | .AsSequence() 103 | .Append(1) 104 | .Prepend(1); 105 | 106 | var triangle = Sequence.Iterate(Sequence.With(1), func); 107 | var sixRows = triangle.Take(6); 108 | 109 | //Assertions 110 | Assert.Equal(6, sixRows.Count); 111 | 112 | _expectedTriangle.Zip(sixRows).ForEach( 113 | rows => Assert.Equal(rows.Item1, rows.Item2)); 114 | } 115 | 116 | [Fact] 117 | public void V4() 118 | { 119 | var triangle = Sequence.Iterate( 120 | Sequence.With(1), //start with row (1), and then... 121 | row => row.Append(0) //shift row to the left 122 | .Zip(row.Prepend(0)) //shift row to the right, and zip both shifted rows 123 | .Select(TupleEx.Sum)); //sum the two shifted rows 124 | 125 | var sixRows = triangle.Take(6); 126 | 127 | //Assertions 128 | Assert.Equal(6, sixRows.Count); 129 | 130 | _expectedTriangle.Zip(sixRows).ForEach( 131 | rows => Assert.Equal(rows.Item1, rows.Item2)); 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/IndexTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace Sequences.Tests 10 | { 11 | public class IndexTests 12 | { 13 | [Fact] 14 | public void Indices_Returns_RangeOfIndices() 15 | { 16 | var sequence = Sequence.Fill(0, 5); 17 | var indices = sequence.Indices(); 18 | int[] expectedIndices = {0, 1, 2, 3, 4}; 19 | 20 | Assert.Equal(expectedIndices, indices); 21 | } 22 | 23 | [Fact] 24 | public void IndexOf_Returns_IndexOfElement() 25 | { 26 | var sequence = Sequence.Range(0, 5); 27 | var index = sequence.IndexOf(3); 28 | 29 | Assert.Equal(3, index); 30 | } 31 | 32 | [Fact] 33 | public void IndexOf_Returns_MinusOne_When_ElementIsNotFound() 34 | { 35 | var sequence = Sequence.Range(0, 5); 36 | var index = sequence.IndexOf(5); 37 | 38 | Assert.Equal(-1, index); 39 | } 40 | 41 | [Fact] 42 | public void IndexOf_Returns_MinusOne_When_ElementIsBeforeFrom() 43 | { 44 | var sequence = Sequence.Range(0, 5); 45 | var index = sequence.IndexOf(2, from: 3); 46 | 47 | Assert.Equal(-1, index); 48 | } 49 | 50 | [Fact] 51 | public void IndexOf_WithCount_Returns_MinusOne_When_ElementIsNotWithinRange() 52 | { 53 | var sequence = Sequence.Range(0, 5); 54 | var index = sequence.IndexOf(3, from: 0, count: 3); 55 | 56 | Assert.Equal(-1, index); 57 | } 58 | 59 | [Fact] 60 | public void IndexWhere_Returns_IndexOfElement() 61 | { 62 | var sequence = Sequence.Range(0, 5); 63 | var index = sequence.IndexWhere(i => i == 3); 64 | 65 | Assert.Equal(3, index); 66 | } 67 | 68 | [Fact] 69 | public void IndexWhere_Returns_MinusOne_When_ElementIsNotFound() 70 | { 71 | var sequence = Sequence.Range(0, 5); 72 | var index = sequence.IndexWhere(i => i == 5); 73 | 74 | Assert.Equal(-1, index); 75 | } 76 | 77 | [Fact] 78 | public void IndexWhere_Returns_MinusOne_When_ElementIsBeforeFrom() 79 | { 80 | var sequence = Sequence.Range(0, 5); 81 | var index = sequence.IndexWhere(i => i == 2, from: 3); 82 | 83 | Assert.Equal(-1, index); 84 | } 85 | 86 | [Fact] 87 | public void IndexWhere_WithCount_Returns_MinusOne_When_ElementIsNotWithinRange() 88 | { 89 | var sequence = Sequence.Range(0, 5); 90 | var index = sequence.IndexWhere(i => i == 3, from: 0, count: 3); 91 | 92 | Assert.Equal(-1, index); 93 | } 94 | 95 | [Fact] 96 | public void LastIndexOf_Returns_IndexOfElement() 97 | { 98 | var sequence = Sequence.With(1, 1, 2, 2, 3, 3); 99 | var index = sequence.LastIndexOf(2); 100 | 101 | Assert.Equal(3, index); 102 | } 103 | 104 | [Fact] 105 | public void LastIndexOf_Returns_MinusOne_When_ElementIsNotFound() 106 | { 107 | var sequence = Sequence.Range(0, 5); 108 | var index = sequence.LastIndexOf(5); 109 | 110 | Assert.Equal(-1, index); 111 | } 112 | 113 | [Fact] 114 | public void LastIndexOf_Returns_MinusOne_When_ElementIsAfterEnd() 115 | { 116 | var sequence = Sequence.Range(0, 5); 117 | var index = sequence.LastIndexOf(4, end: 3); 118 | 119 | Assert.Equal(-1, index); 120 | } 121 | 122 | [Fact] 123 | public void LastIndexOf_WithCount_Returns_MinusOne_When_ElementIsNotWithinRange() 124 | { 125 | var sequence = Sequence.Range(0, 5); 126 | var index = sequence.LastIndexOf(1, end: 3, count: 2); 127 | 128 | Assert.Equal(-1, index); 129 | } 130 | 131 | [Fact] 132 | public void LastIndexWhere_Returns_IndexOfElement() 133 | { 134 | var sequence = Sequence.With(1, 1, 2, 2, 3, 3); 135 | var index = sequence.LastIndexWhere(i => i == 2); 136 | 137 | Assert.Equal(3, index); 138 | } 139 | 140 | [Fact] 141 | public void LastIndexWhere_Returns_MinusOne_When_ElementIsNotFound() 142 | { 143 | var sequence = Sequence.Range(0, 5); 144 | var index = sequence.LastIndexWhere(i => i == 5); 145 | 146 | Assert.Equal(-1, index); 147 | } 148 | 149 | [Fact] 150 | public void LastIndexWhere_Returns_MinusOne_When_ElementIsAfterEnd() 151 | { 152 | var sequence = Sequence.Range(0, 5); 153 | var index = sequence.LastIndexWhere(i => i == 4, end: 3); 154 | 155 | Assert.Equal(-1, index); 156 | } 157 | 158 | [Fact] 159 | public void LastIndexWhere_WithCount_Returns_MinusOne_When_ElementIsNotWithinRange() 160 | { 161 | var sequence = Sequence.Range(0, 5); 162 | var index = sequence.LastIndexWhere(i => i == 1, end: 3, count: 2); 163 | 164 | Assert.Equal(-1, index); 165 | } 166 | 167 | [Fact] 168 | public void Indexer_Returns_ElementAtIndex() 169 | { 170 | var sequence = Sequence.Range(0, 5); 171 | var element = sequence[3]; 172 | 173 | Assert.Equal(3, element); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /docs/Sequence.Docs/Sequence.Docs.shfbproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | Debug 7 | AnyCPU 8 | 2.0 9 | 7807882d-4578-40a2-9e24-970e1e992ec5 10 | 1.9.9.0 11 | 12 | Sequence.Docs 13 | Sequence.Docs 14 | Sequence.Docs 15 | 16 | .NET Portable Library 4.0 %28Legacy%29 17 | ..\Help\ 18 | Sequence.Docs 19 | en-US 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | OnlyWarningsAndErrors 30 | Website 31 | False 32 | True 33 | False 34 | False 35 | True 36 | 37 | 38 | 39 | 2 40 | False 41 | Standard 42 | Blank 43 | False 44 | VS2013 45 | False 46 | Guid 47 | A Sandcastle Documented Class Library 48 | AboveNamespaces 49 | Attributes, InheritedMembers, InheritedFrameworkMembers, Protected, ProtectedInternalAsProtected 50 | diogo.filipe.acastro%40gmail.com 51 | Msdn 52 | Msdn 53 | False 54 | True 55 | ..\help\working\ 56 | 57 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | ..\help\ 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | pascals-triangle 100 | pascals-triangle 101 | 102 | 103 | 104 | 105 | Sequences 106 | {6c9f9e66-e026-4966-aeb2-9b29b5f0df85} 107 | True 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/FactoryMethodsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Numerics; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Moq; 8 | using Xunit; 9 | 10 | namespace Sequences.Tests 11 | { 12 | public class FactoryMethodsTests 13 | { 14 | [Fact] 15 | public void With_WrapsEnumerable() 16 | { 17 | IEnumerable enumerable = new[] {1, 2, 3}; 18 | Assert.Equal(enumerable, Sequence.With(enumerable)); 19 | } 20 | 21 | [Fact] 22 | public void With_WrapsArray() 23 | { 24 | int[] array = {1, 2, 3}; 25 | Assert.Equal(array, Sequence.With(array)); 26 | } 27 | 28 | [Fact] 29 | public void Iterate_RepeatedlyAppliesFunction() 30 | { 31 | var sequence = Sequence.Iterate(1, i => i*2); 32 | int[] expectedSequence = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512}; 33 | 34 | Assert.Equal(expectedSequence, sequence.Take(10)); 35 | } 36 | 37 | [Fact] 38 | public void Iterate_HasGivenLength() 39 | { 40 | var sequence = Sequence.Iterate(1, 10, i => i*2); 41 | int[] expectedSequence = {1, 2, 4, 8, 16, 32, 64, 128, 256, 512}; 42 | 43 | Assert.Equal(10, sequence.Count); 44 | Assert.Equal(expectedSequence, sequence); 45 | } 46 | 47 | [Fact] 48 | public void Tabulate_AppliesFuncToRangeOfIntegers() 49 | { 50 | var sequence = Sequence.Tabulate(5, i => i*2); 51 | int[] expectedSequence = {0, 2, 4, 6, 8}; 52 | 53 | Assert.Equal(expectedSequence, sequence); 54 | } 55 | 56 | [Fact] 57 | public void Fill_RepeatsElement() 58 | { 59 | Assert.Equal(new[] {1, 1, 1}, Sequence.Fill(1, 3)); 60 | } 61 | 62 | [Fact] 63 | public void Fill_ContinuouslyEvaluatesDelegate() 64 | { 65 | //Arrange 66 | var elemFuncMock = new Mock>(); 67 | elemFuncMock.Setup(tail => tail()).Returns(1); 68 | 69 | var sequence = Sequence.Fill(elemFuncMock.Object, 3); 70 | 71 | //realize sequence 72 | var list = sequence.ToList(); 73 | 74 | //Assert 75 | elemFuncMock.Verify(f => f(), Times.Exactly(3)); 76 | } 77 | 78 | [Fact] 79 | public void Continually_ContinuouslyRepeatsElement() 80 | { 81 | var sequence = Sequence.Continually(1); 82 | 83 | Assert.True(sequence 84 | .Take(10) 85 | .All(i => i == 1)); 86 | } 87 | 88 | [Fact] 89 | public void Continually_ContinuouslyEvaluatesDelegate() 90 | { 91 | //Arrange 92 | var elemFuncMock = new Mock>(); 93 | elemFuncMock.Setup(tail => tail()).Returns(1); 94 | 95 | var sequence = Sequence.Continually(elemFuncMock.Object); 96 | 97 | //realize sequence 98 | var list = sequence.Take(10).ToList(); 99 | 100 | //Assert 101 | elemFuncMock.Verify(f => f(), Times.AtLeast(10)); 102 | } 103 | 104 | [Fact] 105 | public void FromInt_Generates_ConsecutiveIntegers() 106 | { 107 | int[] expected = {1, 2, 3, 4, 5}; 108 | 109 | var intSequence = Sequence.From(1); 110 | var longSequence = Sequence.From(1L); 111 | var bigIntSequence = Sequence.From((BigInteger) 1); 112 | 113 | Assert.Equal(expected, intSequence.Take(5)); 114 | Assert.Equal(expected.Select(i => (long) i), longSequence.Take(5)); 115 | Assert.Equal(expected.Select(i => (BigInteger) i), bigIntSequence.Take(5)); 116 | } 117 | 118 | [Fact] 119 | public void FromInt_WithStep_Generates_IncreasingSequence() 120 | { 121 | int[] expected = {0, 5, 10, 15, 20}; 122 | 123 | var intSequence = Sequence.From(0, 5); 124 | var longSequence = Sequence.From(0L, 5); 125 | var bigIntSequence = Sequence.From((BigInteger) 0, 5); 126 | 127 | Assert.Equal(expected, intSequence.Take(5)); 128 | Assert.Equal(expected.Select(i => (long) i), longSequence.Take(5)); 129 | Assert.Equal(expected.Select(i => (BigInteger) i), bigIntSequence.Take(5)); 130 | } 131 | 132 | [Fact] 133 | public void Range_Generates_FiniteSequence() 134 | { 135 | int[] expected = {0, 5, 10, 15, 20}; 136 | 137 | var intSequence = Sequence.Range(0, 25, 5); 138 | var longSequence = Sequence.Range(0L, 25L, 5L); 139 | var bigIntSequence = Sequence.Range((BigInteger) 0, 25, 5); 140 | 141 | Assert.Equal(expected, intSequence); 142 | Assert.Equal(expected.Select(i => (long) i), longSequence); 143 | Assert.Equal(expected.Select(i => (BigInteger) i), bigIntSequence); 144 | } 145 | 146 | [Fact] 147 | public void Range_Excludes_UpperBound() 148 | { 149 | Assert.DoesNotContain(25, Sequence.Range(0, 25, 5)); 150 | Assert.DoesNotContain(25, Sequence.Range(0L, 25, 5)); 151 | Assert.DoesNotContain(25, Sequence.Range((BigInteger) 0, 25, 5)); 152 | } 153 | 154 | [Fact] 155 | public void Range_WithNegativeStep_Excludes_UpperBound() 156 | { 157 | Assert.DoesNotContain(0, Sequence.Range(25, 0, -5)); 158 | Assert.DoesNotContain(0, Sequence.Range(25L, 0, -5)); 159 | Assert.DoesNotContain(0, Sequence.Range((BigInteger) 25, 0, -5)); 160 | } 161 | 162 | [Fact] 163 | public void Range_When_StartEqualsEnd_Generates_EmptySequence() 164 | { 165 | Assert.Empty(Sequence.Range(0, 0, 0)); 166 | Assert.Empty(Sequence.Range(0L, 0, 0)); 167 | Assert.Empty(Sequence.Range((BigInteger) 0, 0, 0)); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Functional/Sequences.Tests.Functional.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {C0460AD0-BE50-4A91-BBDF-BDBA5F2634C6} 7 | Library 8 | Properties 9 | Sequences.Tests.Functional 10 | Sequences.Tests.Functional 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | ..\ 20 | true 21 | 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | 32 | 33 | pdbonly 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | 40 | 41 | bin\Docs\ 42 | TRACE 43 | true 44 | pdbonly 45 | AnyCPU 46 | prompt 47 | MinimumRecommendedRules.ruleset 48 | 49 | 50 | 51 | 52 | 53 | ..\..\packages\xunit.1.9.2\lib\net20\xunit.dll 54 | 55 | 56 | ..\..\packages\xunit.extensions.1.9.2\lib\net20\xunit.extensions.dll 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | {6c9f9e66-e026-4966-aeb2-9b29b5f0df85} 80 | Sequences 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | False 91 | 92 | 93 | False 94 | 95 | 96 | False 97 | 98 | 99 | False 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 118 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/SequenceBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Moq; 7 | using Xunit; 8 | 9 | namespace Sequences.Tests 10 | { 11 | public class SequenceBuilderTests 12 | { 13 | [Fact] 14 | public void ResultSequence_IncludesAppendedCollections() 15 | { 16 | var collection1 = new List {1, 2, 3}; 17 | var collection2 = Sequence.Range(4, 7); 18 | 19 | var builder = Sequence.NewBuilder() 20 | .Append(collection1) 21 | .Append(collection2); 22 | 23 | Assert.Equal(new[] {1, 2, 3, 4, 5, 6}, builder.ToSequence()); 24 | } 25 | 26 | [Fact] 27 | public void ResultSequence_IncludesAppendedLazyCollections() 28 | { 29 | var collection1 = new List {1, 2, 3}; 30 | var collection2 = Sequence.Range(4, 7); 31 | 32 | var builder = Sequence.NewBuilder() 33 | .Append(() => collection1) 34 | .Append(() => collection2); 35 | 36 | Assert.Equal(new[] {1, 2, 3, 4, 5, 6}, builder.ToSequence()); 37 | } 38 | 39 | [Fact] 40 | public void ResultSequence_IncludesAppendedElements() 41 | { 42 | var builder = Sequence.NewBuilder() 43 | .Append(1) 44 | .Append(2, 3); 45 | 46 | Assert.Equal(new[] {1, 2, 3}, builder.ToSequence()); 47 | } 48 | 49 | [Fact] 50 | public void ResultSequence_IncludesAppendedLazyElements() 51 | { 52 | var builder = Sequence.NewBuilder() 53 | .Append(() => 1); 54 | 55 | Assert.Equal(new[] {1}, builder.ToSequence()); 56 | } 57 | 58 | [Fact] 59 | public void ResultSequence_LazilyEvaluates_AppendedCollectionsContents() 60 | { 61 | var inputSequence = Sequence.Range(4, 7); 62 | 63 | var builder = Sequence.NewBuilder() 64 | .Append(inputSequence); 65 | 66 | var resultSequence = builder.ToSequence(); 67 | 68 | //assert that the input sequence hasn't been fully realized yet 69 | Assert.False(inputSequence.IsTailDefined); 70 | } 71 | 72 | [Fact] 73 | public void ResultSequence_LazilyEvaluates_AppendedCollectionsAndElements() 74 | { 75 | //setup mocks for lazy elements/colection 76 | var lazyElemMock1 = new Mock>(); 77 | lazyElemMock1.Setup(elem => elem()).Returns(1); 78 | 79 | var lazyElemMock2 = new Mock>(); 80 | lazyElemMock2.Setup(elem => elem()).Returns(2); 81 | 82 | var lazyCollectionMock = new Mock>>(); 83 | lazyCollectionMock.Setup(xs => xs()).Returns(new[] {3, 4, 5}); 84 | 85 | var builder = Sequence.NewBuilder() 86 | .Append(lazyElemMock1.Object) 87 | .Append(lazyElemMock2.Object) 88 | .Append(lazyCollectionMock.Object); 89 | 90 | var iter = builder.ToSequence().GetEnumerator(); 91 | 92 | //head is eagerly evaluated - all other members shouldn't have been evaluated yet. 93 | lazyElemMock1.Verify(e => e(), Times.Once); 94 | lazyElemMock2.Verify(e => e(), Times.Never); 95 | lazyCollectionMock.Verify(xs => xs(), Times.Never); 96 | 97 | //move to the 1st element 98 | iter.MoveNext(); 99 | 100 | //state shouldn't have changed 101 | lazyElemMock1.Verify(e => e(), Times.Once); 102 | lazyElemMock2.Verify(e => e(), Times.Never); 103 | lazyCollectionMock.Verify(xs => xs(), Times.Never); 104 | 105 | //move to the 2nd element 106 | iter.MoveNext(); 107 | 108 | //lazyElemMock2 should have been evaluated by now 109 | lazyElemMock1.Verify(e => e(), Times.Once); 110 | lazyElemMock2.Verify(e => e(), Times.Once); 111 | lazyCollectionMock.Verify(xs => xs(), Times.Never); 112 | 113 | //move to the 3rd element 114 | iter.MoveNext(); 115 | 116 | //lazyCollectionMock should have been evaluated by now 117 | lazyElemMock1.Verify(e => e(), Times.Once); 118 | lazyElemMock2.Verify(e => e(), Times.Once); 119 | lazyCollectionMock.Verify(xs => xs(), Times.Once); 120 | } 121 | 122 | [Fact] 123 | public void ResultSequence_IgnoresFurtherClears() 124 | { 125 | var builder = Sequence.NewBuilder() 126 | .Append(1, 2, 3); 127 | 128 | var sequence = builder.ToSequence(); 129 | 130 | //clear 131 | builder.Clear(); 132 | 133 | Assert.Equal(new[] {1, 2, 3}, sequence); 134 | } 135 | 136 | [Fact] 137 | public void ResultSequence_IgnoresFurtherAppends() 138 | { 139 | var builder = Sequence.NewBuilder() 140 | .Append(1, 2, 3); 141 | 142 | var sequence = builder.ToSequence(); 143 | 144 | //append more elements 145 | builder.Append(9); 146 | 147 | Assert.DoesNotContain(9, sequence); 148 | } 149 | 150 | [Fact] 151 | public void Clear_ClearsBuffer() 152 | { 153 | var builder = Sequence.NewBuilder() 154 | .Append(1) 155 | .Clear(); 156 | 157 | Assert.Empty(builder.ToSequence()); 158 | } 159 | 160 | [Fact] 161 | public void AppendParams_ThrowsException_When_InputIsNull() 162 | { 163 | Assert.Throws(() => Sequence.NewBuilder().Append(null as int[])); 164 | } 165 | 166 | [Fact] 167 | public void AppendEnumerable_ThrowsException_When_InputIsNull() 168 | { 169 | Assert.Throws(() => Sequence.NewBuilder().Append(null as IEnumerable)); 170 | } 171 | 172 | [Fact] 173 | public void OperatorAdd_AppendsElement() 174 | { 175 | var builder = Sequence.NewBuilder() + 1 + 2 + 3; 176 | 177 | Assert.Equal(new[] {1, 2, 3}, builder.ToSequence()); 178 | } 179 | 180 | [Fact] 181 | public void OperatorAdd_AppendsCollection() 182 | { 183 | var builder = Sequence.NewBuilder() + 184 | Sequence.With(1) + 185 | Sequence.With(2) + 186 | Sequence.With(3); 187 | 188 | 189 | Assert.Equal(new[] {1, 2, 3}, builder.ToSequence()); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/Sequences.Tests.Unit.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {97B797FB-B2D8-414B-9459-71EB27113D60} 7 | Library 8 | Properties 9 | Sequences.Tests 10 | Sequences.Tests 11 | v4.5 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | ..\..\ 20 | true 21 | 22 | 23 | true 24 | full 25 | false 26 | bin\Debug\ 27 | DEBUG;TRACE 28 | prompt 29 | 4 30 | 31 | 32 | pdbonly 33 | true 34 | bin\Release\ 35 | TRACE 36 | prompt 37 | 4 38 | 39 | 40 | bin\Docs\ 41 | TRACE 42 | true 43 | pdbonly 44 | AnyCPU 45 | prompt 46 | MinimumRecommendedRules.ruleset 47 | 48 | 49 | 50 | ..\..\packages\Moq.4.2.1402.2112\lib\net40\Moq.dll 51 | 52 | 53 | 54 | 55 | False 56 | ..\..\packages\xunit.1.9.2\lib\net20\xunit.dll 57 | 58 | 59 | False 60 | ..\..\packages\xunit.extensions.1.9.2\lib\net20\xunit.extensions.dll 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | {6c9f9e66-e026-4966-aeb2-9b29b5f0df85} 106 | Sequences 107 | 108 | 109 | 110 | 111 | 112 | 113 | False 114 | 115 | 116 | False 117 | 118 | 119 | False 120 | 121 | 122 | False 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sequences 2 | 3 | Sequences is a port of Scala's [`Stream[+A]`][3] to C#. 4 | 5 | A `Sequence` is an immutable lazy list whose elements are only evaluated when they are needed. A sequence is composed by a *head* (the first element) and a lazily-evaluated *tail* (the remaining elements). 6 | 7 | The fact that the tail is lazily-evaluated, makes it easy to represent infinite series or sets. For example, here's how to represent the set of all natural numbers. 8 | 9 | 10 | ```cs 11 | public ISequence Naturals(int start) 12 | { 13 | return new Sequence( head: start, 14 | tail: () => Naturals(start + 1)); 15 | } 16 | 17 | var naturals = Naturals(1); 18 | 19 | //take the first 5 natural numbers 20 | naturals.Take(5).ForEach(Console.Write); //prints 12345 21 | ``` 22 | 23 | Or, even simpler: 24 | 25 | ```cs 26 | var naturals = Sequence.From(1); 27 | ``` 28 | 29 | Sequences also features memoization, i.e., the sequence stores previously computed values to avoid re-evaluation. 30 | 31 | ```cs 32 | //start with number 1, and then keep adding 2 to the previous number 33 | var odds = Sequence.Iterate(1, odd => 34 | { 35 | Console.WriteLine("Adding " + odd + " + 2"); 36 | return odd + 2; 37 | }); 38 | 39 | odds.Take(3).ForEach(Console.WriteLine); 40 | odds.Take(5).ForEach(Console.WriteLine); 41 | 42 | //prints 43 | //1 44 | //Adding 1 + 2 45 | //3 46 | //Adding 3 + 2 47 | //5 48 | 49 | //and then 50 | //1 51 | //3 52 | //5 53 | //Adding 5 + 2 54 | //7 55 | //Adding 7 + 2 56 | //9 57 | ``` 58 | 59 | You can iterate through an infinite sequence for as long as you want. As long as you don't hold onto its head, each sequence will be elected for garbage collection as soon as you move to the next value. This prevents an infinite sequence from occupying a large and growing ammount of memory. 60 | 61 | ```cs 62 | foreach (var odd in Sequence.Iterate(1, odd => odd + 2)) 63 | { 64 | //when you move to Sequence(11, ?), 65 | //the previous Sequence(9, ?) is elected for collection. 66 | } 67 | ``` 68 | 69 | ## Examples 70 | 71 | The above natural numbers example is very simple. But Sequences allow for so much more. So let's explore some more complex examples. 72 | 73 | #### Fibonacci sequence 74 | 75 | The Fibonacci sequence is a famous series in mathematics, where each fibonacci number is defined as the sum of the two previous fibonacci numbers, i.e. `F(n) = F(n-1) + F(n-2)`, with seed values `F(0) = 0` and `F(1) = 1`. 76 | 77 | In scala, the fibonacci sequence is commonly expressed as follows: 78 | 79 | ```scala 80 | val fibs: Stream[Int] = 0 #:: 1 #:: fibs.zip(fibs.tail).map { n => n._1 + n._2 } 81 | ``` 82 | 83 | In C#, the syntax is a little more verbose, but still readable: 84 | 85 | ```cs 86 | Func, int> sum = pair => pair.Item1 + pair.Item2; 87 | 88 | ISequence fibs = null; 89 | 90 | fibs = Sequence.With(0, 1) //start with (0, 1, ?) 91 | .Concat(() => //and then 92 | fibs.Zip(fibs.Tail) //zip the sequence with its tail (i.e., (0,1), (1,1), (1,2), (2,3), (3, 5)) 93 | .Select(sum)); //select the sum of each pair (i.e., 1, 2, 3, 5, 8) 94 | ``` 95 | 96 | The code above creates more objects than needed. The following implementation shows a more efficient way of representing the fibonacci sequence: 97 | 98 | ```cs 99 | using System.Numerics; 100 | 101 | //current and next are any two consecutive fibonacci numbers. 102 | ISequence Fibs(BigInteger current, BigInteger next) 103 | { 104 | return new Sequence(current, () => Fibs(next, current + next)); 105 | } 106 | 107 | var fibs = Fibs(0, 1); 108 | 109 | //prints 0 1 1 2 3 5 8 13 21 34 110 | fibs.Take(10).ForEach(Console.WriteLine); 111 | ``` 112 | 113 | #### Prime numbers 114 | 115 | One way to find every prime number in a given range is to use the [Sieve of Eratosthenes][4]. 116 | To find the prime numbers up to 100, a slight variation of the sieve goes like this: 117 | 118 | 1. Start with a list representing the range [2, 100]. 119 | 2. Let *p* be the head of the list. 120 | 3. Take *p* as the next prime number, and remove every multiple of *p* from the list. 121 | 4. If the list is empty: 122 | * stop; 123 | * otherwise, repeat from step 2. 124 | 125 | Here's a way of implementing the sieve as a sequence. 126 | 127 | ```cs 128 | var range = Sequence.Range(2, 101); 129 | var primes = PrimesWithin(range); 130 | 131 | //prints: 2 3 5 7 11 132 | Console.WriteLine(primes.Take(5).MkString(" ")); 133 | 134 | public ISequence PrimesWithin(ISequence range) 135 | { 136 | if (range.IsEmpty) 137 | return Sequence.Empty(); 138 | 139 | //take the next prime number 140 | var p = range.Head; 141 | 142 | //skip p, and remove further multiples of p 143 | var filtered = range.Tail.Where(num => num % p != 0).Force(); 144 | 145 | return new Sequence(p, () => PrimesWithin(filtered)); 146 | } 147 | ``` 148 | 149 | #### Pascal's Triangle 150 | 151 | Everyone knows the famous [Pascal's Triangle][6]. 152 | 153 | ![Pascal's Triangle][pascal] 154 | 155 | The triangle starts with a 1 at the top. In every other row, each number is the sum of the two directly above it. 156 | 157 | There are all sorts of ways of representing Pascal's triangle using sequences, but here's an interesting one: 158 | 159 | ```cs 160 | Func, int> sum = pair => pair.Item1 + pair.Item2; 161 | 162 | Func, ISequence> rowFactory = 163 | row => row.Append(0) //shift row to the left 164 | .Zip(row.Prepend(0)) //shift row to the right, and zip both shifted rows 165 | .Select(sum); //sum the two shifted rows 166 | 167 | var triangle = Sequence.Iterate( 168 | start: Sequence.With(1), 169 | func: rowFactory); 170 | ``` 171 | 172 | You start with row (1). From then on, every row is computed by shifting the row to the right, shifting the row to the left, zipping both shifted rows together and producing the sum of each tuple. For example, given the row (1, 3, 3, 1): 173 | 174 | ``` 175 | 0 1 3 3 1 //shift right 176 | 1 3 3 1 0 //shift left 177 | ↓ ↓ ↓ ↓ ↓ 178 | 1 4 6 4 1 179 | ``` 180 | 181 | For more examples, refer to the [functional tests project][1]. 182 | 183 | ## Limitations 184 | 185 | Due to a limitation in [generic type constraints][5] as of C# 5.0, sequences are *not* covariant, as opposed to Scala's `Stream[+A]`. 186 | 187 | Take the signature of `copyToBuffer` in Scala as an example: 188 | 189 | ```scala 190 | copyToBuffer[B >: A](dest: Buffer[B]): Unit 191 | ``` 192 | 193 | The constraint `B >: A` (read *A derives from B*) cannot be expressed in C# (even though the opposite can be expressed as `where B : A` or *B derives from A*) unless `A` is also one of *copyToBuffer*'s type parameters - which it isn't. 194 | 195 | ## Documentation 196 | Documentation is available at [dcastro.github.io/Sequences][2]. 197 | 198 | ## NuGet 199 | To install [Sequences][7], run the following command in the Package Manager Console 200 | 201 | ``` 202 | PM> Install-Package Sequences 203 | ``` 204 | 205 | [1]: https://github.com/dcastro/Sequences/tree/master/tests/Sequences.Tests.Functional 206 | [2]: http://diogocastro.com/Sequences 207 | [3]: http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.Stream 208 | [4]: http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes 209 | [5]: http://msdn.microsoft.com/en-gb/library/d5x73970.aspx 210 | [6]: http://en.wikipedia.org/wiki/Pascal's_triangle 211 | [7]: https://www.nuget.org/packages/Sequences/ 212 | [pascal]: https://raw.githubusercontent.com/dcastro/Sequences/master/docs/Sequence.Docs/Media/pascals-triangle.png 213 | -------------------------------------------------------------------------------- /.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | 32 | 33 | 34 | 35 | $(SolutionDir).nuget 36 | 37 | 38 | 39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 41 | 42 | 43 | 44 | $(MSBuildProjectDirectory)\packages.config 45 | $(PackagesProjectConfig) 46 | 47 | 48 | 49 | 50 | $(NuGetToolsPath)\NuGet.exe 51 | @(PackageSource) 52 | 53 | "$(NuGetExePath)" 54 | mono --runtime=v4.0.30319 $(NuGetExePath) 55 | 56 | $(TargetDir.Trim('\\')) 57 | 58 | -RequireConsent 59 | -NonInteractive 60 | 61 | "$(SolutionDir) " 62 | "$(SolutionDir)" 63 | 64 | 65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 67 | 68 | 69 | 70 | RestorePackages; 71 | $(BuildDependsOn); 72 | 73 | 74 | 75 | 76 | $(BuildDependsOn); 77 | BuildPackage; 78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/EnumerableTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Moq; 8 | using Xunit; 9 | 10 | namespace Sequences.Tests 11 | { 12 | public class EnumerableTests 13 | { 14 | [Fact] 15 | public void Enumerator_IteratesThroughElements() 16 | { 17 | var sequence = new Sequence(1, () => 18 | new Sequence(2, () => 19 | new Sequence(3, Sequence.Empty))); 20 | 21 | Assert.Equal(new[] {1, 2, 3}, sequence); 22 | } 23 | 24 | [Fact] 25 | public void ForEach_AppliesFunctionToEachElement() 26 | { 27 | var funcMock = new Mock>(); 28 | var sequence = Sequence.Range(1, 4); 29 | 30 | sequence.ForEach(funcMock.Object); 31 | 32 | funcMock.Verify(f => f(It.IsAny()), Times.Exactly(3)); 33 | 34 | funcMock.Verify(f => f(1), Times.Exactly(1)); 35 | funcMock.Verify(f => f(2), Times.Exactly(1)); 36 | funcMock.Verify(f => f(3), Times.Exactly(1)); 37 | } 38 | 39 | [Fact] 40 | public void Enumerator_AllowsGCToCollectSequences() 41 | { 42 | var seqReference = new WeakReference>( 43 | Sequence.From(1)); 44 | 45 | ISequence temp; 46 | Assert.True(seqReference.TryGetTarget(out temp)); 47 | 48 | IEnumerator iter = temp.GetEnumerator(); 49 | temp = null; 50 | 51 | iter.MoveNext(); //move to the original sequence - Sequence(1, ?) 52 | iter.MoveNext(); //move to the second sequence - Sequence(2, ?) 53 | 54 | //collect 55 | GC.Collect(); 56 | 57 | //the iterator should no longer hold a reference to the original Sequence(1, ?) 58 | //and, so, the GC should have been able to clear the sequence the WeakReference points to. 59 | Assert.False(seqReference.TryGetTarget(out temp)); 60 | 61 | /** 62 | * Keep the iterator alive. 63 | * If the iterator was collected, the above assertion could be a "false positive" - the weak reference could have been collected 64 | * not because the iterator allows it, but because the iterator was *also* collected. 65 | */ 66 | GC.KeepAlive(iter); 67 | } 68 | 69 | [Fact] 70 | public void SlidingEnumerator_AllowsGCToCollectSequences() 71 | { 72 | var seqReference = new WeakReference>( 73 | Sequence.From(1)); 74 | 75 | ISequence temp; 76 | Assert.True(seqReference.TryGetTarget(out temp)); 77 | 78 | IEnumerator> iter = temp.Sliding(2).GetEnumerator(); 79 | temp = null; 80 | 81 | iter.MoveNext(); //move to the first sliding window - Sequence(1, 2) 82 | iter.MoveNext(); //move to the second sliding window - Sequence(2, 3) 83 | 84 | //collect 85 | GC.Collect(); 86 | 87 | //the iterator should no longer hold a reference to the original Sequence(1, ?) 88 | //and, so, the GC should have been able to clear the sequence the WeakReference points to. 89 | Assert.False(seqReference.TryGetTarget(out temp)); 90 | 91 | /** 92 | * Keep the iterator alive. 93 | * If the iterator was collected, the above assertion could be a "false positive" - the weak reference could have been collected 94 | * not because the iterator allows it, but because the iterator was *also* collected. 95 | */ 96 | GC.KeepAlive(iter); 97 | } 98 | 99 | [Fact] 100 | public void GroupedEnumerator_AllowsGCToCollectSequences() 101 | { 102 | var seqReference = new WeakReference>( 103 | Sequence.From(1)); 104 | 105 | ISequence temp; 106 | Assert.True(seqReference.TryGetTarget(out temp)); 107 | 108 | IEnumerator> iter = temp.Grouped(2).GetEnumerator(); 109 | temp = null; 110 | 111 | iter.MoveNext(); //move to the first group - Sequence(1, 2) 112 | iter.MoveNext(); //move to the second group - Sequence(3, 4) 113 | 114 | //collect 115 | GC.Collect(); 116 | 117 | //the iterator should no longer hold a reference to the original Sequence(1, ?) 118 | //and, so, the GC should have been able to clear the sequence the WeakReference points to. 119 | Assert.False(seqReference.TryGetTarget(out temp)); 120 | 121 | /** 122 | * Keep the iterator alive. 123 | * If the iterator was collected, the above assertion could be a "false positive" - the weak reference could have been collected 124 | * not because the iterator allows it, but because the iterator was *also* collected. 125 | */ 126 | GC.KeepAlive(iter); 127 | } 128 | 129 | [Fact] 130 | public void NonEmptyTailsEnumerator_AllowsGCToCollectSequences() 131 | { 132 | var seqReference = new WeakReference>( 133 | Sequence.From(1)); 134 | 135 | ISequence temp; 136 | Assert.True(seqReference.TryGetTarget(out temp)); 137 | 138 | IEnumerator> iter = temp.NonEmptyTails().GetEnumerator(); 139 | temp = null; 140 | 141 | iter.MoveNext(); //move to the sequence itself - Sequence(1, ?) 142 | iter.MoveNext(); //move to its tail - Sequence(2, ?) 143 | 144 | //collect 145 | GC.Collect(); 146 | 147 | //the iterator should no longer hold a reference to the original Sequence(1, ?) 148 | //and, so, the GC should have been able to clear the sequence the WeakReference points to. 149 | Assert.False(seqReference.TryGetTarget(out temp)); 150 | 151 | /** 152 | * Keep the iterator alive. 153 | * If the iterator was collected, the above assertion could be a "false positive" - the weak reference could have been collected 154 | * not because the iterator allows it, but because the iterator was *also* collected. 155 | */ 156 | GC.KeepAlive(iter); 157 | } 158 | 159 | [Fact] 160 | public void TailsEnumerator_AllowsGCToCollectSequences() 161 | { 162 | var seqReference = new WeakReference>( 163 | Sequence.From(1)); 164 | 165 | ISequence temp; 166 | Assert.True(seqReference.TryGetTarget(out temp)); 167 | 168 | IEnumerator> iter = temp.Tails().GetEnumerator(); 169 | temp = null; 170 | 171 | iter.MoveNext(); //move to the sequence itself - Sequence(1, ?) 172 | iter.MoveNext(); //move to its tail - Sequence(2, ?) 173 | 174 | //collect 175 | GC.Collect(); 176 | 177 | //the iterator should no longer hold a reference to the original Sequence(1, ?) 178 | //and, so, the GC should have been able to clear the sequence the WeakReference points to. 179 | Assert.False(seqReference.TryGetTarget(out temp)); 180 | 181 | /** 182 | * Keep the iterator alive. 183 | * If the iterator was collected, the above assertion could be a "false positive" - the weak reference could have been collected 184 | * not because the iterator allows it, but because the iterator was *also* collected. 185 | */ 186 | GC.KeepAlive(iter); 187 | } 188 | 189 | [Fact] 190 | public void IndicesEnumerator_AllowsGCToCollectSequences() 191 | { 192 | var seqReference = new WeakReference>( 193 | Sequence.From(1)); 194 | 195 | ISequence temp; 196 | Assert.True(seqReference.TryGetTarget(out temp)); 197 | 198 | IEnumerator iter = temp.Indices().GetEnumerator(); 199 | temp = null; 200 | 201 | iter.MoveNext(); 202 | iter.MoveNext(); 203 | 204 | //collect 205 | GC.Collect(); 206 | 207 | //the iterator should no longer hold a reference to the original Sequence(1, ?) 208 | //and, so, the GC should have been able to clear the sequence the WeakReference points to. 209 | Assert.False(seqReference.TryGetTarget(out temp)); 210 | 211 | /** 212 | * Keep the iterator alive. 213 | * If the iterator was collected, the above assertion could be a "false positive" - the weak reference could have been collected 214 | * not because the iterator allows it, but because the iterator was *also* collected. 215 | */ 216 | GC.KeepAlive(iter); 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /docs/Sequence.Docs/Content/Examples.aml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | In , we saw how to represent sequences of natural and odd numbers. 22 | In this section, we take a look at a few more complex examples. 23 | 24 | 25 | 26 | For more examples, refer to the 27 | functional tests project 28 | https://github.com/dcastro/Sequences/tree/master/tests/Sequences.Tests.Functional 29 | . 30 | 31 | 32 | 33 |
34 | Fibonacci sequence 35 | 36 | 37 | The Fibonacci sequence is a famous series in mathematics, 38 | where each fibonacci number is defined as the sum of the two previous fibonacci numbers, 39 | i.e. F(n) = F(n-1) + F(n-2), with seed values F(0) = 0 and F(1) = 1. 40 | 41 | 42 | 43 | In scala, the fibonacci sequence is commonly expressed as follows: 44 | 45 | 46 | 47 | n._1 + n._2 }]]> 49 | 50 | 51 | 52 | In C#, the syntax is a little more verbose, but still readable: 53 | 54 | 55 | 56 | , int> sum = pair => pair.Item1 + pair.Item2; 58 | 59 | ISequence fibs = null; 60 | 61 | fibs = Sequence.With(0, 1) //start with (0, 1, ?) 62 | .Concat(() => //and then 63 | fibs.Zip(fibs.Tail) //zip the sequence with its tail (i.e., (0,1), (1,1), (1,2), (2,3), (3, 5)) 64 | .Select(sum)); //select the sum of each pair (i.e., 1, 2, 3, 5, 8)]]> 65 | 66 | 67 | 68 | The code above creates more objects than needed. The following implementation shows a more efficient way of representing the fibonacci sequence: 69 | 70 | 71 | 72 | Fibs(BigInteger current, BigInteger next) 77 | { 78 | return new Sequence(current, () => Fibs(next, current + next)); 79 | } 80 | 81 | var fibs = Fibs(0, 1); 82 | 83 | //prints 0 1 1 2 3 5 8 13 21 34 84 | fibs.Take(10).ForEach(Console.WriteLine);]]> 85 | 86 | 87 | 88 | 104 |
105 | 106 |
107 | Prime numbers 108 | 109 | 110 | One way to find every prime number in a given range is to use the 111 | Sieve of Eratosthenes 112 | http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes 113 | . 114 | To find the prime numbers up to 100, a slight variation of the sieve goes like this: 115 | 116 | 117 | 118 | 119 | Start with a list representing the range [2, 100]. 120 | 121 | 122 | Let p be the head of the list. 123 | 124 | 125 | Take p as the next prime number, and remove every multiple of p from the list. 126 | 127 | 128 | If the list is empty: 129 | 130 | 131 | stop; 132 | 133 | 134 | otherwise, repeat from step 2. 135 | 136 | 137 | 138 | 139 | 140 | 141 | Here's a way of implementing the sieve as a sequence. 142 | 143 | 144 | 145 | PrimesWithin(ISequence range) 153 | { 154 | if (range.IsEmpty) 155 | return Sequence.Empty(); 156 | 157 | //take the next prime number 158 | var p = range.Head; 159 | 160 | //skip p, and remove further multiples of p 161 | var filtered = range.Tail.Where(num => num % p != 0).Force(); 162 | 163 | return new Sequence(p, () => PrimesWithin(filtered)); 164 | }]]> 165 | 166 | 167 | 168 |
169 | 170 |
171 | Pascal's Triangle 172 | 173 | 174 | Everyone knows the famous 175 | Pascal's Triangle 176 | http://en.wikipedia.org/wiki/Pascal's_triangle 177 | . 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | The triangle starts with a 1 at the top. In every other row, each number is the sum of the two directly above it. 186 | 187 | 188 | 189 | There are all sorts of ways of representing Pascal's triangle using sequences, but here's an interesting one: 190 | 191 | 192 | 193 | , int> sum = pair => pair.Item1 + pair.Item2; 195 | 196 | Func, ISequence> rowFactory = 197 | row => row.Append(0) //shift row to the left 198 | .Zip(row.Prepend(0)) //shift row to the right, and zip both shifted rows 199 | .Select(sum); //sum the two shifted rows 200 | 201 | var triangle = Sequence.Iterate( 202 | start: Sequence.With(1), 203 | func: rowFactory);]]> 204 | 205 | 206 | 207 | You start with row (1). 208 | From then on, every row is computed by shifting the row to the right, shifting the row to the left, zipping both shifted rows together and producing the sum of each tuple. 209 | For example, given the row (1, 3, 3, 1): 210 | 211 | 212 | 213 | 218 | 219 | 220 | 221 |
222 | 223 | 224 | T:Sequences.ISequence`1 225 | T:Sequences.Sequence 226 | 227 |
228 |
229 | -------------------------------------------------------------------------------- /src/Sequences/Sequence`1.Iterators.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Sequences 9 | { 10 | public partial class Sequence 11 | { 12 | 13 | /** 14 | * An "iterator block" lets the compiler generate an for us. 15 | * However, that enumerator holds onto the sequence that created it, and doesn't let the GC collect it. 16 | * 17 | * These specialized iterators don't do that. 18 | * Instead, when they move to a sequence's tail, they replace the reference to the sequence with a reference to its tail, 19 | * letting GC collect the original sequence. 20 | * 21 | * This lets us, for example, iterate through an infinite sequence for as long as we want, e.g., foreach(var e in Sequence.From(1L)) { }. 22 | * A compiler-generated iterator would keep a reference to the first sequence - Sequence(1, ?) - and, therefore, to all its tails. 23 | * Using these iterators, the GC will be able to collect all intermediate sequences as the loop progresses. 24 | */ 25 | 26 | private class TailsIterator : IEnumerator 27 | { 28 | private ISequence _seq; 29 | private readonly Func, TElem> _selector; 30 | private readonly bool _returnEmptyTail; 31 | private bool _hasMoved; 32 | private bool _hasFinished; 33 | 34 | public TailsIterator(ISequence seq, Func, TElem> selector, bool returnEmptyTail) 35 | { 36 | _seq = seq; 37 | _selector = selector; 38 | _returnEmptyTail = returnEmptyTail; 39 | } 40 | 41 | public bool MoveNext() 42 | { 43 | //check if the iterator has reached the end of the sequence 44 | if (_hasFinished) 45 | return false; 46 | 47 | //move to the sequence's tail on every call but the first 48 | if (_hasMoved) 49 | _seq = _seq.Tail; 50 | else 51 | _hasMoved = true; 52 | 53 | //check if the iterator has reached the end of the sequence 54 | if (_seq.IsEmpty) 55 | _hasFinished = true; 56 | 57 | if (_seq.NonEmpty || _returnEmptyTail) 58 | { 59 | Current = _selector(_seq); 60 | return true; 61 | } 62 | 63 | return false; 64 | } 65 | 66 | void IEnumerator.Reset() 67 | { 68 | throw new NotSupportedException(); 69 | } 70 | 71 | public TElem Current { get; private set; } 72 | 73 | object IEnumerator.Current 74 | { 75 | get { return Current; } 76 | } 77 | 78 | public void Dispose() 79 | { 80 | } 81 | } 82 | 83 | private class IndexIterator : IEnumerator 84 | { 85 | private readonly IEnumerator _iter; 86 | private int _index; 87 | private bool _hasFinished; 88 | 89 | public IndexIterator(IEnumerable seq) 90 | { 91 | _iter = seq.GetEnumerator(); 92 | } 93 | 94 | public bool MoveNext() 95 | { 96 | if (_hasFinished || !_iter.TryMoveNext()) 97 | { 98 | _hasFinished = true; 99 | return false; 100 | } 101 | 102 | Current = _index++; 103 | return true; 104 | } 105 | 106 | void IEnumerator.Reset() 107 | { 108 | throw new NotSupportedException(); 109 | } 110 | 111 | public int Current { get; private set; } 112 | 113 | object IEnumerator.Current 114 | { 115 | get { return Current; } 116 | } 117 | 118 | public void Dispose() 119 | { 120 | } 121 | } 122 | 123 | private class SlidingIterator : IEnumerator> 124 | { 125 | private ISequence _seq; 126 | private readonly int _size; 127 | private readonly int _step; 128 | 129 | private bool _hasMoved; 130 | private bool _hasMoreElems; 131 | 132 | private readonly List _buffer; 133 | 134 | public SlidingIterator(ISequence seq, int size, int step) 135 | { 136 | _seq = seq; 137 | _size = size; 138 | _step = step; 139 | 140 | _buffer = new List(_size); 141 | _hasMoreElems = _seq.NonEmpty; 142 | } 143 | 144 | public bool MoveNext() 145 | { 146 | //move "_step" elements on every call but the first 147 | if (_hasMoved) 148 | _seq = _seq.Skip(_step); 149 | else 150 | _hasMoved = true; 151 | 152 | //in addition to checking if the previous iterator had more elements, 153 | //we also need to check if the sequence is still not empty after advancing "_step" elements 154 | _hasMoreElems &= _seq.NonEmpty; 155 | 156 | if (!_hasMoreElems) 157 | return false; 158 | 159 | //group elements into a buffer 160 | var iterator = _seq.GetEnumerator(); 161 | 162 | for (int i = 0; i < _size && iterator.TryMoveNext(); i++) 163 | _buffer.Add(iterator.Current); 164 | 165 | //force the evaluation of the buffer's contents, before we clear the buffer. 166 | Current = _buffer.AsSequence().Force(); 167 | _buffer.Clear(); 168 | 169 | //check if there are any more elements 170 | _hasMoreElems = iterator.TryMoveNext(); 171 | 172 | return true; 173 | } 174 | 175 | void IEnumerator.Reset() 176 | { 177 | throw new NotSupportedException(); 178 | } 179 | 180 | public ISequence Current { get; private set; } 181 | 182 | object IEnumerator.Current 183 | { 184 | get { return Current; } 185 | } 186 | 187 | public void Dispose() 188 | { 189 | } 190 | } 191 | 192 | private class GroupedIterator : IEnumerator> 193 | { 194 | private ISequence _seq; 195 | private readonly int _size; 196 | 197 | private bool _hasMoved; 198 | 199 | public GroupedIterator(ISequence seq, int size) 200 | { 201 | _seq = seq; 202 | _size = size; 203 | } 204 | 205 | public bool MoveNext() 206 | { 207 | if (_hasMoved) 208 | _seq = _seq.Skip(_size); 209 | else 210 | _hasMoved = true; 211 | 212 | if (_seq.IsEmpty) 213 | return false; 214 | 215 | Current = _seq.Take(_size); 216 | return true; 217 | } 218 | 219 | void IEnumerator.Reset() 220 | { 221 | throw new NotSupportedException(); 222 | } 223 | 224 | public ISequence Current { get; private set; } 225 | 226 | object IEnumerator.Current 227 | { 228 | get { return Current; } 229 | } 230 | 231 | public void Dispose() 232 | { 233 | } 234 | } 235 | 236 | private class CombinationsEnumerable : IEnumerable> 237 | { 238 | private readonly ISequence _sequence; 239 | private readonly int _size; 240 | 241 | public CombinationsEnumerable(ISequence sequence, int size) 242 | { 243 | _sequence = sequence; 244 | _size = size; 245 | } 246 | 247 | public IEnumerator> GetEnumerator() 248 | { 249 | if (_size == 0) 250 | yield return Sequence.Empty(); 251 | else 252 | //combine each distinct element (subsequence.Head) 253 | //with each possible combination of the remaining elements (tailCombination) 254 | foreach (var subSequence in _sequence.NonEmptyTails().Distinct(new CompareByHead())) 255 | foreach (var tailCombination in subSequence.Tail.Combinations(_size - 1)) 256 | yield return new Sequence(subSequence.Head, () => tailCombination); 257 | } 258 | 259 | IEnumerator IEnumerable.GetEnumerator() 260 | { 261 | return GetEnumerator(); 262 | } 263 | 264 | /// 265 | /// Compares two sequences by their heads. The implementation assumes input sequences are not empty. 266 | /// 267 | private class CompareByHead : IEqualityComparer> 268 | { 269 | private readonly IEqualityComparer _headComparer = EqualityComparer.Default; 270 | 271 | public bool Equals(ISequence x, ISequence y) 272 | { 273 | return _headComparer.Equals(x.Head, y.Head); 274 | } 275 | 276 | public int GetHashCode(ISequence seq) 277 | { 278 | return seq.Head.GetHashCode(); 279 | } 280 | } 281 | } 282 | 283 | private class PermutationsEnumerable : IEnumerable> 284 | { 285 | private readonly ISequence _sequence; 286 | 287 | public PermutationsEnumerable(ISequence sequence) 288 | { 289 | _sequence = sequence; 290 | } 291 | 292 | public IEnumerator> GetEnumerator() 293 | { 294 | if (_sequence.IsEmpty) 295 | yield return Sequence.Empty(); 296 | else 297 | foreach (var elem in _sequence.Distinct()) 298 | foreach (var restPermutation in _sequence.Remove(elem).Permutations()) 299 | yield return new Sequence(elem, () => restPermutation); 300 | } 301 | 302 | IEnumerator IEnumerable.GetEnumerator() 303 | { 304 | return GetEnumerator(); 305 | } 306 | } 307 | 308 | private class GenericEnumerable : IEnumerable 309 | { 310 | private readonly Func> _iteratorFactory; 311 | 312 | public GenericEnumerable(Func> iteratorFactory) 313 | { 314 | _iteratorFactory = iteratorFactory; 315 | } 316 | 317 | public IEnumerator GetEnumerator() 318 | { 319 | return _iteratorFactory(); 320 | } 321 | 322 | IEnumerator IEnumerable.GetEnumerator() 323 | { 324 | return GetEnumerator(); 325 | } 326 | } 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /tests/Sequences.Tests.Unit/ExtensionMethodsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Numerics; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | 10 | namespace Sequences.Tests 11 | { 12 | public class ExtensionMethodsTests 13 | { 14 | private readonly IEnumerable _enumerable; 15 | private readonly ISequence _sequence; 16 | 17 | public ExtensionMethodsTests() 18 | { 19 | _enumerable = new[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10}; 20 | _sequence = _enumerable.AsSequence(); 21 | } 22 | 23 | [Fact] 24 | public void Skip_ReturnsRemainingElements() 25 | { 26 | var sequence = Sequence.Range(1, 6, 1); 27 | Assert.Equal(new[] {3, 4, 5}, sequence.Skip(2)); 28 | } 29 | 30 | [Fact] 31 | public void Skip_ReturnsSameSequence_When_CountIsZero() 32 | { 33 | var sequence = Sequence.Range(1, 6, 1); 34 | Assert.Same(sequence, sequence.Skip(0)); 35 | } 36 | 37 | [Fact] 38 | public void Skip_ReturnsSameSequence_When_CountIsNegative() 39 | { 40 | var sequence = Sequence.Range(1, 6, 1); 41 | Assert.Same(sequence, sequence.Skip(-5)); 42 | } 43 | 44 | [Fact] 45 | public void Skip_ReturnsEmptySequence_When_CountIsHigherThanLength() 46 | { 47 | var sequence = Sequence.Range(1, 6, 1); 48 | Assert.True(sequence.Skip(10).IsEmpty); 49 | } 50 | 51 | [Fact] 52 | public void Skip_ReturnsEmptySequence_When_CountIsEqualToLength() 53 | { 54 | var sequence = Sequence.Range(1, 6, 1); 55 | Assert.True(sequence.Skip(5).IsEmpty); 56 | } 57 | 58 | [Fact] 59 | public void Skip_ThrowsException_When_SourceIsNull() 60 | { 61 | Assert.Throws(() => (null as ISequence).Skip(1)); 62 | } 63 | 64 | [Fact] 65 | public void SkipWhile_ReturnsRemainingElements() 66 | { 67 | var sequence = Sequence.Range(1, 6, 1); 68 | Assert.Equal(new[] {4, 5}, sequence.SkipWhile(i => i <= 3)); 69 | } 70 | 71 | [Fact] 72 | public void SkipWhile_RetursSameSequence_When_PredicateFailsForFirstElem() 73 | { 74 | var sequence = Sequence.Range(1, 6, 1); 75 | Assert.Same(sequence, sequence.SkipWhile(i => i < 0)); 76 | } 77 | 78 | [Fact] 79 | public void SkipWhile_ReturnsEmptySequence_When_PredicateSucceedsForAllElements() 80 | { 81 | var sequence = Sequence.Range(1, 6, 1); 82 | Assert.True(sequence.SkipWhile(i => i < 10).IsEmpty); 83 | } 84 | 85 | [Fact] 86 | public void SkipWhile_ThrowsException_When_SourceIsNull() 87 | { 88 | Assert.Throws(() => (null as ISequence).SkipWhile(i => true)); 89 | } 90 | 91 | [Fact] 92 | public void SkipWhile_ThrowsException_When_PredicateIsNull() 93 | { 94 | Assert.Throws( 95 | () => Sequence.With(1) 96 | .SkipWhile(null as Func)); 97 | } 98 | 99 | [Fact] 100 | public void SkipWhile_WithIndex_ReturnsRemainingElements() 101 | { 102 | var sequence = Sequence.Range(1, 6, 1); 103 | Assert.Equal(new[] {4, 5}, sequence.SkipWhile((i, index) => index <= 2)); 104 | } 105 | 106 | [Fact] 107 | public void SkipWhile_WithIndex_RetursSameSequence_When_PredicateFailsForFirstElem() 108 | { 109 | var sequence = Sequence.Range(1, 6, 1); 110 | Assert.Same(sequence, sequence.SkipWhile((i, index) => index < 0)); 111 | } 112 | 113 | [Fact] 114 | public void SkipWhile_WithIndex_ReturnsEmptySequence_When_PredicateSucceedsForAllElements() 115 | { 116 | var sequence = Sequence.Range(1, 6, 1); 117 | Assert.True(sequence.SkipWhile((i, index) => index <= 4).IsEmpty); 118 | } 119 | 120 | [Fact] 121 | public void SkipWhile_WithIndex_ThrowsException_When_SourceIsNull() 122 | { 123 | Assert.Throws(() => (null as ISequence).SkipWhile((i, index) => true)); 124 | } 125 | 126 | [Fact] 127 | public void SkipWhile_WithIndex_ThrowsException_When_PredicateIsNull() 128 | { 129 | Assert.Throws( 130 | () => Sequence.With(1) 131 | .SkipWhile(null as Func)); 132 | } 133 | 134 | [Fact] 135 | public void Select() 136 | { 137 | Assert.Equal(_enumerable.Select(i => i + 1), 138 | _sequence.Select(i => i + 1)); 139 | } 140 | 141 | [Fact] 142 | public void Select_With_Index() 143 | { 144 | Assert.Equal(_enumerable.Select((i, index) => i + index), 145 | _sequence.Select((i, index) => i + index)); 146 | } 147 | 148 | [Fact] 149 | public void SelectMany() 150 | { 151 | Assert.Equal(_enumerable.SelectMany(i => new[] {i, i + 1}), 152 | _sequence.SelectMany(i => new[] {i, i + 1})); 153 | } 154 | 155 | [Fact] 156 | public void SelectMany_With_Index() 157 | { 158 | Assert.Equal(_enumerable.SelectMany((i, index) => new[] {i, index}), 159 | _sequence.SelectMany((i, index) => new[] {i, index})); 160 | } 161 | 162 | [Fact] 163 | public void SelectMany_With_IntermediateCollection() 164 | { 165 | Assert.Equal(_enumerable.SelectMany(i => new[] {i, i + 1}, 166 | (i1, i2) => i1 + i2), 167 | _sequence.SelectMany(i => new[] {i, i + 1}, 168 | (i1, i2) => i1 + i2)); 169 | } 170 | 171 | [Fact] 172 | public void SelectMany_With_IntermediateCollection_With_Index() 173 | { 174 | Assert.Equal(_enumerable.SelectMany((i, index) => new[] {i, index}, 175 | (i1, i2) => i1 + i2), 176 | _sequence.SelectMany((i, index) => new[] {i, index}, 177 | (i1, i2) => i1 + i2)); 178 | } 179 | 180 | [Fact] 181 | public void Where() 182 | { 183 | Assert.Equal(_enumerable.Where(i => i%2 == 0), 184 | _sequence.Where(i => i%2 == 0)); 185 | } 186 | 187 | [Fact] 188 | public void Where_With_Index() 189 | { 190 | Assert.Equal(_enumerable.Where((i, index) => (i + index)%2 == 0), 191 | _sequence.Where((i, index) => (i + index)%2 == 0)); 192 | } 193 | 194 | [Fact] 195 | public void Take() 196 | { 197 | Assert.Equal(_enumerable.Take(3), 198 | _sequence.Take(3)); 199 | } 200 | 201 | [Fact] 202 | public void TakeWhile() 203 | { 204 | Assert.Equal(_enumerable.TakeWhile(i => i < 5), 205 | _sequence.TakeWhile(i => i < 5)); 206 | } 207 | 208 | [Fact] 209 | public void TakeWhile_With_Index() 210 | { 211 | Assert.Equal(_enumerable.TakeWhile((i, index) => index < 5), 212 | _sequence.TakeWhile((i, index) => index < 5)); 213 | } 214 | 215 | [Fact] 216 | public void Zip_With_Selector() 217 | { 218 | Assert.Equal(_enumerable.Zip(_enumerable.Skip(1), Tuple.Create), 219 | _sequence.Zip(_sequence.Tail, Tuple.Create)); 220 | } 221 | 222 | [Fact] 223 | public void Distinct() 224 | { 225 | Assert.Equal(_enumerable.Distinct(), 226 | _sequence.Distinct()); 227 | } 228 | 229 | [Fact] 230 | public void Distinct_With_Comparer() 231 | { 232 | Assert.Equal(_enumerable.Distinct(new SillyIntComparer()), 233 | _sequence.Distinct(new SillyIntComparer())); 234 | } 235 | 236 | [Fact] 237 | public void Except() 238 | { 239 | Assert.Equal(_enumerable.Except(new[] {1, 2, 3}), 240 | _sequence.Except(new[] {1, 2, 3})); 241 | } 242 | 243 | [Fact] 244 | public void Except_With_Comparer() 245 | { 246 | Assert.Equal(_enumerable.Except(new[] {1, 2, 3}, new SillyIntComparer()), 247 | _sequence.Except(new[] {1, 2, 3}, new SillyIntComparer())); 248 | } 249 | 250 | [Fact] 251 | public void Intersect() 252 | { 253 | Assert.Equal(_enumerable.Intersect(new[] {9, 10, 11, 12}), 254 | _sequence.Intersect(new[] {9, 10, 11, 12})); 255 | } 256 | 257 | [Fact] 258 | public void Intersect_With_Comparer() 259 | { 260 | Assert.Equal(_enumerable.Intersect(new[] {9, 10, 11, 12}, new SillyIntComparer()), 261 | _sequence.Intersect(new[] {9, 10, 11, 12}, new SillyIntComparer())); 262 | } 263 | 264 | [Fact] 265 | public void Reverse() 266 | { 267 | Assert.Equal(_enumerable.Reverse(), _sequence.Reverse()); 268 | } 269 | 270 | [Fact] 271 | public void Union() 272 | { 273 | Assert.Equal(_enumerable.Union(new[] {9, 10, 11, 12}), 274 | _sequence.Union(new[] {9, 10, 11, 12})); 275 | } 276 | 277 | [Fact] 278 | public void Union_With_Comparer() 279 | { 280 | Assert.Equal(_enumerable.Union(new[] {9, 10, 11, 12}, new SillyIntComparer()), 281 | _sequence.Union(new[] {9, 10, 11, 12}, new SillyIntComparer())); 282 | } 283 | 284 | [Fact] 285 | public void SumBigInteger() 286 | { 287 | Assert.Equal(10, Sequence.With(0, 2, 5, 3).Sum()); 288 | } 289 | 290 | [Fact] 291 | public void SumBigInteger_With_Selector() 292 | { 293 | Assert.Equal(10, Sequence.With("", "hello", "world") 294 | .Sum(str => (BigInteger) str.Length)); 295 | } 296 | 297 | [Fact] 298 | public void SumNullableBigInteger() 299 | { 300 | Assert.Equal(10, Sequence.With(null, 0, null, 2, 5, 3, null).Sum()); 301 | } 302 | 303 | [Fact] 304 | public void SumNullableIntegers_WithNoValues() 305 | { 306 | Assert.Equal(new int?[] {null}.Sum(), 307 | new BigInteger?[] {null}.AsSequence().Sum()); 308 | } 309 | 310 | [Fact] 311 | public void SumNullableBigInteger_With_Selector() 312 | { 313 | Assert.Equal(10, Sequence.With(null, "", null, "hello", "world", null) 314 | .Sum(str => str == null ? null : (BigInteger?) str.Length)); 315 | } 316 | 317 | private class SillyIntComparer : IEqualityComparer 318 | { 319 | public bool Equals(int x, int y) 320 | { 321 | return true; 322 | } 323 | 324 | public int GetHashCode(int obj) 325 | { 326 | return 1; 327 | } 328 | } 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/Sequences/Sequence.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.Contracts; 4 | using System.Linq; 5 | using System.Numerics; 6 | using System.Text; 7 | 8 | namespace Sequences 9 | { 10 | /// 11 | /// Provides a set of static methods for creating instances of . 12 | /// 13 | [Pure] 14 | public static partial class Sequence 15 | { 16 | /// 17 | /// Returns an empty sequence. 18 | /// 19 | /// The type parameter of the returned sequence. 20 | /// An empty sequence. 21 | [Pure] 22 | public static ISequence Empty() 23 | { 24 | return EmptySequence.Instance; 25 | } 26 | 27 | /// 28 | /// Creates a new sequence builder. 29 | /// 30 | /// The type of the element in the sequence. 31 | /// A new sequence builder. 32 | [Pure] 33 | public static SequenceBuilder NewBuilder() 34 | { 35 | return new SequenceBuilder(); 36 | } 37 | 38 | /// 39 | /// Creates a sequence from a given array. 40 | /// 41 | /// The type of elements in the sequence. 42 | /// The elements for which a sequence will be created. 43 | /// A sequence created from the elements in . 44 | [Pure] 45 | public static ISequence With(params T[] items) 46 | { 47 | return With(items.AsEnumerable()); 48 | } 49 | 50 | /// 51 | /// Creates a sequence from a given enumerable. 52 | /// 53 | /// The type of elements in the sequence. 54 | /// The enumerable to be evaluated. 55 | /// A sequence created by lazily-evaluating . 56 | [Pure] 57 | public static ISequence With(IEnumerable items) 58 | { 59 | if (items == null) throw new ArgumentNullException("items"); 60 | return items as ISequence ?? With(items.GetEnumerator()); 61 | } 62 | 63 | private static ISequence With(IEnumerator iterator) 64 | { 65 | return iterator.TryMoveNext() 66 | ? new Sequence(iterator.Current, () => With(iterator)) 67 | : Empty(); 68 | } 69 | 70 | /// 71 | /// Creates an infinite sequence that repeatedly applies a given function to a start value. 72 | /// 73 | /// The type of the elements in the sequence. 74 | /// The first value of the sequence. 75 | /// The function that's repeatedly applied to the last element to produce the next element. 76 | /// An infinite sequence obtained by repeatedly applying to . 77 | [Pure] 78 | public static ISequence Iterate(T start, Func func) 79 | { 80 | if (func == null) throw new ArgumentNullException("func"); 81 | return new Sequence(start, () => Iterate(func(start), func)); 82 | } 83 | 84 | /// 85 | /// Creates a finite sequence of a given length that repeatedly applies a given function to a start value. 86 | /// 87 | /// The type of the elements in the sequence. 88 | /// The first value of the sequence. 89 | /// The number of elements in the sequence. 90 | /// The function that's repeatedly applied to the last element to produce the next element. 91 | /// A finite sequence of length obtained by repeatedly applying to . 92 | [Pure] 93 | public static ISequence Iterate(T start, int length, Func func) 94 | { 95 | if (func == null) throw new ArgumentNullException("func"); 96 | 97 | return length <= 0 98 | ? Empty() 99 | : new Sequence(start, () => Iterate(func(start), length - 1, func)); 100 | } 101 | 102 | /// 103 | /// Creates a sequence obtained by applying a given function over a range of integer values starting at 0. 104 | /// 105 | /// The type of the elements in the sequence. 106 | /// The number of elements in the collection. 107 | /// The function used to produce the elements. 108 | /// A sequence obtained by applying over a range of integer values from 0 to - 1. 109 | [Pure] 110 | public static ISequence Tabulate(int length, Func func) 111 | { 112 | if (func == null) throw new ArgumentNullException("func"); 113 | return Tabulate(length, 0, func); 114 | } 115 | 116 | private static ISequence Tabulate(int length, int current, Func func) 117 | { 118 | return current >= length 119 | ? Empty() 120 | : new Sequence(func(current), () => Tabulate(length, current + 1, func)); 121 | } 122 | 123 | /// 124 | /// Creates a finite sequence containing the given element a number of times. 125 | /// 126 | /// The type of elements in the sequence. 127 | /// The delegate to be repeatedly evaluated. 128 | /// The number of times to repeat . 129 | /// A sequence containg number of . 130 | [Pure] 131 | public static ISequence Fill(Func elem, int count) 132 | { 133 | if (elem == null) throw new ArgumentNullException("elem"); 134 | return (count > 0) 135 | ? new Sequence(elem(), () => Fill(elem, count - 1)) 136 | : Empty(); 137 | } 138 | 139 | /// 140 | /// Creates a finite sequence containing the given element a number of times. 141 | /// 142 | /// The type of elements in the sequence. 143 | /// The element to be repeated. 144 | /// The number of times to repeat . 145 | /// A sequence containg number of . 146 | [Pure] 147 | public static ISequence Fill(T elem, int count) 148 | { 149 | return Fill(() => elem, count); 150 | } 151 | 152 | /// 153 | /// Create an infinite sequence containing the given element expression (which is computed for each occurrence). 154 | /// 155 | /// The type of elements in the sequence. 156 | /// A delegate that will be continuously evaluated. 157 | /// A sequence containing an infinite number of elements returned by the delegate. 158 | [Pure] 159 | public static ISequence Continually(Func elem) 160 | { 161 | if (elem == null) throw new ArgumentNullException("elem"); 162 | return new Sequence(elem(), () => Continually(elem)); 163 | } 164 | 165 | /// 166 | /// Create an infinite sequence containing the given element. 167 | /// 168 | /// The type of elements in the sequence. 169 | /// The element to be continuously repeated. 170 | /// A sequence containing an infinite number of 171 | [Pure] 172 | public static ISequence Continually(T elem) 173 | { 174 | return Continually(() => elem); 175 | } 176 | 177 | /// 178 | /// Creates an infinite sequence starting at and incrementing by in each step. 179 | /// 180 | /// The start value of the sequence. 181 | /// The value to increment in each step (positive or negative). 182 | /// A sequence starting at value . 183 | [Pure] 184 | public static ISequence From(int start, int step) 185 | { 186 | return new Sequence(start, () => From(start + step, step)); 187 | } 188 | 189 | /// 190 | /// Creates an infinite sequence starting at and incrementing by 1 in each step. 191 | /// 192 | /// The start value of the sequence. 193 | /// A sequence starting at value . 194 | [Pure] 195 | public static ISequence From(int start) 196 | { 197 | return From(start, 1); 198 | } 199 | 200 | /// 201 | /// Creates an infinite sequence starting at and incrementing by in each step. 202 | /// 203 | /// The start value of the sequence. 204 | /// The value to increment in each step (positive or negative). 205 | /// A sequence starting at value . 206 | [Pure] 207 | public static ISequence From(long start, long step) 208 | { 209 | return new Sequence(start, () => From(start + step, step)); 210 | } 211 | 212 | /// 213 | /// Creates an infinite sequence starting at and incrementing by 1 in each step. 214 | /// 215 | /// The start value of the sequence. 216 | /// A sequence starting at value . 217 | [Pure] 218 | public static ISequence From(long start) 219 | { 220 | return From(start, 1); 221 | } 222 | 223 | /// 224 | /// Creates an infinite sequence starting at and incrementing by in each step. 225 | /// 226 | /// The start value of the sequence. 227 | /// The value to increment in each step (positive or negative). 228 | /// A sequence starting at value . 229 | [Pure] 230 | public static ISequence From(BigInteger start, BigInteger step) 231 | { 232 | return new Sequence(start, () => From(start + step, step)); 233 | } 234 | 235 | /// 236 | /// Creates an infinite sequence starting at and incrementing by 1 in each step. 237 | /// 238 | /// The start value of the sequence. 239 | /// A sequence starting at value . 240 | [Pure] 241 | public static ISequence From(BigInteger start) 242 | { 243 | return From(start, 1); 244 | } 245 | 246 | /// 247 | /// Creates a sequence containing equally spaced values in some integer interval. 248 | /// 249 | /// The start value of the collection. 250 | /// The exclusive upper bound. 251 | /// The value to increment in each step (positive or negative). 252 | /// A collection with values , + , ... 253 | /// up to, but excluding . 254 | [Pure] 255 | public static ISequence Range(int start, int end, int step) 256 | { 257 | if ((step > 0 && start >= end) || 258 | (step <= 0 && start <= end)) 259 | return Empty(); 260 | 261 | return new Sequence(start, () => Range(start + step, end, step)); 262 | } 263 | 264 | /// 265 | /// Creates a sequence containing equally spaced values in some integer interval. 266 | /// 267 | /// The start value of the collection. 268 | /// The exclusive upper bound. 269 | /// A collection with values , + 1, ... 270 | /// up to, but excluding . 271 | [Pure] 272 | public static ISequence Range(int start, int end) 273 | { 274 | return Range(start, end, 1); 275 | } 276 | 277 | /// 278 | /// Creates a sequence containing equally spaced values in some integer interval. 279 | /// 280 | /// The start value of the collection. 281 | /// The exclusive upper bound. 282 | /// The value to increment in each step (positive or negative). 283 | /// A collection with values , + , ... 284 | /// up to, but excluding . 285 | [Pure] 286 | public static ISequence Range(long start, long end, long step) 287 | { 288 | if ((step > 0 && start >= end) || 289 | (step <= 0 && start <= end)) 290 | return Empty(); 291 | 292 | return new Sequence(start, () => Range(start + step, end, step)); 293 | } 294 | 295 | /// 296 | /// Creates a sequence containing equally spaced values in some integer interval. 297 | /// 298 | /// The start value of the collection. 299 | /// The exclusive upper bound. 300 | /// A collection with values , + 1, ... 301 | /// up to, but excluding . 302 | [Pure] 303 | public static ISequence Range(long start, long end) 304 | { 305 | return Range(start, end, 1); 306 | } 307 | 308 | /// 309 | /// Creates a sequence containing equally spaced values in some integer interval. 310 | /// 311 | /// The start value of the collection. 312 | /// The exclusive upper bound. 313 | /// The value to increment in each step (positive or negative). 314 | /// A collection with values , + , ... 315 | /// up to, but excluding . 316 | [Pure] 317 | public static ISequence Range(BigInteger start, BigInteger end, BigInteger step) 318 | { 319 | if ((step > 0 && start >= end) || 320 | (step <= 0 && start <= end)) 321 | return Empty(); 322 | 323 | return new Sequence(start, () => Range(start + step, end, step)); 324 | } 325 | 326 | /// 327 | /// Creates a sequence containing equally spaced values in some integer interval. 328 | /// 329 | /// The start value of the collection. 330 | /// The exclusive upper bound. 331 | /// A collection with values , + 1, ... 332 | /// up to, but excluding . 333 | [Pure] 334 | public static ISequence Range(BigInteger start, BigInteger end) 335 | { 336 | return Range(start, end, 1); 337 | } 338 | } 339 | } 340 | --------------------------------------------------------------------------------