SkillAgentSearch skills...

Endless

🌌 Extensions that support the C# functional paradigm.

Install / Use

/learn @tompazourek/Endless

README

Endless logo Endless .NET

Build status Tests codecov NuGet version NuGet downloads

Extensions that support the C# functional paradigm.

The library is written in C# and released with an MIT license, so feel free to fork or use commercially.

Any feedback is appreciated, please visit the issues page or send me an e-mail.

Download

Binaries of the last build can be downloaded on the AppVeyor CI page of the project.

The library is also published on NuGet.org (prerelease), install using:

PM> Install-Package Endless -Pre

<sup>Endless is built for .NET v4.0 and .NET Standard 1.1.</sup>

Table of contents

Generate

Iterate

The basic function for creating infinite sequences. Can be widely used in algorithms that are based on any kind of iteration.

Creates an infinite list where the first item is calculated by applying the function on the second argument, the second item by applying the function on the previous result and so on.

Sample usages:

DateTime NextFridayThe13th()
{
    var future = DateTime.Today.Iterate(x => x.AddDays(1)); // future in a variable
    return future.First(x => x.Day == 13 && x.DayOfWeek == DayOfWeek.Friday);
}
IEnumerable<int> enumerable = 2.Iterate(x => x * x).Take(5); // yields 2, 4, 16, 256, 65536

There is also overload for Iterate, which uses binary function taking two seeds and generating new value using two previous values.

Sample usage:

Generate.Iterate(0, 1, (a, b) => a + b); // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ... (Fibonacci sequence)

Repeat

Creates an infinite list where all items are the first argument.

Sample usage:

IEnumerable<int> ones = 1.Repeat(); // yields 1, 1, 1, ...

Instead of constant value, the repeated value can be also generated by function:

var random = new Random();
IEnumerable<int> enumerable = new Func<int>(random.Next).Repeat().Take(10); // yields 10 random numbers

Cycle

Creates an infinite list by repeating the given sequence.

Sample usage:

IEnumerable<int> enumerable = new[] { 1, 2, 3 }.Cycle(); // yields 1, 2, 3, 1, 2, 3, 1, 2, ...

Yield

Creates IEnumerable containing one item. Mostly used as syntactic sugar or to write more readable one-liners.

Simple usage:

IEnumerable<int> five = 5.Yield();

Can be also used in functions that are returning IEnumerable and we cannot use yield keyword:

IEnumerable<int> GetNumbers()
{
    if (some condition)
        return 0.Yield();
    
    return someOtherSequence;
}

This code would have to be written without the Yield() function in two separate ways:

IEnumerable<int> GetNumbers()
{
    if (some condition)
        return new [] { 0 };
    
    return someOtherSequence;
}
IEnumerable<int> GetNumbers()
{
    if (some condition)
        yield return 0;
    
    foreach(var item in someOtherSequence)
    {
        yield return item;
    }
}

Enumerate

Enumerate extensions allow more functionality than the standard library function Enumerable.Range.

Natural numbers

Simple sequences to generate infinite sequence of natural numbers:

IEnumerable<int> numbers1 = Natural.Numbers; // yields 1, 2, 3, ...
IEnumerable<int> numbers2 = Natural.NumbersWithZero; // yields 0, 1, 2, 3, ...
IEnumerable<BigInteger> numbers3 = BigNatural.Numbers; // yields 1, 2, 3, ...
IEnumerable<BigInteger> numbers4 = BigNatural.NumbersWithZero; // yields 0, 1, 2, 3, ...

Sample usage:

// Find the sum of all the multiples of 3 or 5 below 1000.
int sum = Natural.Numbers.TakeUntil(1000).Where(x => x % 3 == 0 || x % 5 == 0).Sum();

From, Then, To

Set of generic enumerators to create useful finite or infinite sequences. The argument is implemented dynamically, it is tested with BigInteger, byte, char, decimal, double, float, int, long, but should work with other types too (even custom ones that can be added/subtracted and compared). Also there is a support for DateTime and DateTimeOffset.

From

Enumerate.From(1); // yields 1, 2, 3, ...
Enumerate.From('a'); // yields 'a', 'b', 'c', ...
Enumerate.From(new BigInteger(10)); // yields 10, 11, 12, ...
Enumerate.From((byte) 250); // yields 250, 251, 252, 253, 254, 255, 0, 1, ...
Enumerate.From(new DateTime(1990, 7, 5)); // yields 1990-07-05, 1990-07-06, 1990-07-07, 1990-07-08, ...

From + Then

Next number is generated by adding the distance of the given two values.

Enumerate.From('a').Then('c'); // yields 'a', 'c', 'e', 'g', 'i', ...
Enumerate.From(1d).Then(0.9); // yields 1, 0.9, 0.8, 0.7, 0.6, ...
Enumerate.From(new BigInteger(1)).Then(3); // yields 1, 3, 5, 7, 9, ...
Enumerate.From(new DateTime(1990, 7, 5, 12, 0, 0)).Then(new DateTime(1990, 7, 5, 13, 0, 0)); // yields 1990-07-05 12:00, 1990-07-05 13:00, 1990-07-05 14:00, ...

From + To

The sequence is bounded by the high value.

Enumerate.From('a').To('e'); // yields 'a', 'b', 'c', 'd', 'e'
Enumerate.From('e').To('a'); // yields nothing
Enumerate.From(1M).To(5.5M); // yields 1, 2, 3, 4, 5
Enumerate.From(new DateTime(2014, 1, 1)).To(new DateTime(2014, 31, 1)); // yields 2014-01-01, 2014-01-02, ..., 2014-01-31

From + Then + To

Combines both principles above. The sequence is bounded by the highest/lowest value depending on the increasing/decreasing sequence.

Enumerate.From(1d).Then(0.5).To(-1); // yields 1, 0.5, 0, -0.5, -1
Enumerate.From(1).Then(10).To(40); // yields 1, 10, 19, 28, 37
Enumerate.From(1M).Then(1.1M).To(2); // yields 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2

Haskell-like list comprehensions

Using this syntax, it is easier to write Haskell-like list comprehensions in C#.

Haskell sample:

take 10 [ (i,j) | i <- [1..], let k = i*i, j <- [1..k]]

C# equivalent:

(from i in Enumerate.From(1)
 let k = i * i
 from j in Enumerate.From(1).To(k)
 select new { i, j }).Take(10);

Reduce

Right aggregation

The library provides implementation of AggregateRight funciton with both overloads (with or without seed). The difference to original Aggregate function is that it is evaluated from the right-hand side.

The reduce function must be binary. When using aggregations without seed value, the reduce function requires input and output of the same type.

new[] { 5, 6, 7, 8 }.Aggregate(2, (x, y) => x * y);      // evaluates as: (((2 * 5) * 6) * 7) * 8
new[] { 5, 6, 7, 8 }.Aggregate((x, y) => x * y);         // evaluates as: ((5 * 6) * 7) * 8
new[] { 5, 6, 7, 8 }.AggregateRight(2, (x, y) => x * y); // evaluates as: 5 * (6 * (7 * (8 * 2)))
new[] { 5, 6, 7, 8 }.AggregateRight((x, y) => x * y);    // evaluates as: 5 * (6 * (7 * 8))

In this example it the results will not change depending on the use of right or left aggregation, since the aggregating function is associative. But when using non-associative function like division or matrix multiplication, the results will change.

var squareMatrices = new[] { m1, m2, m3, m4 };
squareMatrices.Aggregate(identityMatrix, multiply); // evaluates as: multiply(multiply(multiply(multiply(identityMatrix, m1), m2), m3), m4)
squareMatrices.AggregateRight(identityMatrix, multiply); // evaluates as: multiply(m1, multiply(m2, multiply(m3, multiply(m4, identityMatrix))))

Scans

Scan reductions are similar to the aggregations above, but instead of returning a final value, they return a list of all the intermediate values.

There are two types of scans:

  • Scan - evaluated from the left-hand side, either without seed or with seed at the beginning
  • ScanRight - evaluated from the right-hand side, either without seed or with seed at the end

The reduce function must be binary. When using scans without a seed value, the reduce function requires input and output of the same type.

var sequence = { x0, x1, x2, x3, ..., xn-2, xn-1, xn }; // pseudo-code
sequence.Scan(seed, f);    // seed, f(seed, x0

Related Skills

View on GitHub
GitHub Stars66
CategoryCustomer
Updated3mo ago
Forks5

Languages

C#

Security Score

97/100

Audited on Dec 10, 2025

No findings