Endless
🌌 Extensions that support the C# functional paradigm.
Install / Use
/learn @tompazourek/EndlessREADME
Endless .NET
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
- Reduce
- Custom IEnumerable extensions
- Existing IEnumerable extensions variations
- Existing IEnumerable extensions overloads
- Stream extensions
- String extensions
- Random extensions
- Functional features
- Samples
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 beginningScanRight- 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
openhue
340.5kControl Philips Hue lights and scenes via the OpenHue CLI.
sag
340.5kElevenLabs text-to-speech with mac-style say UX.
weather
340.5kGet current weather and forecasts via wttr.in or Open-Meteo
tweakcc
1.5kCustomize Claude Code's system prompts, create custom toolsets, input pattern highlighters, themes/thinking verbs/spinners, customize input box & user message styling, support AGENTS.md, unlock private/unreleased features, and much more. Supports both native/npm installs on all platforms.
