Up-to-date version: IEnumerable and Predicate Sets in .NET (Complete, Explained).
Previously: Countable and uncountable sets in .NET (clean version).
Disclaimer: Please do not interpret my attempts to gain maximum from .NET as some kind of socially unacceptable behavior. You, probably, know someone who does it to his/her car. It is a very good way to learn and explore a hospital nearby :)
Update: Set operations *
, -
have been changed to &
,|
as they are more common for expressing set operations.
Reason to have sets: Just because it streamlines IEnumerable<T>
filtering. I will probably define some kind of lambda language for set composition, but look how easy and natural it is to play with them in integration scenarios (online demo):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections;
using static System.Console;
using static System.String;
using static Selection;
class Program
{
static Set<double> Less(double max) =>
new Set<double>(v => v < max);
static Set<double> Bigger(double min) =>
new Set<double>(v => v > min);
static void Main(string[] args)
{
var soundings = Sounding.Fakes();
// business layer – not happy; definitely need some lambda
// language for composition
Set<double> negative = Less(0);
// @ t3chb0t look at the end of the line – this one is for you!
// Also my first attempt to define pipe operator in C# :)
Set<Point> shallow = negative & Bigger(-20) & Select((Point p) => p.Depth);
Set<Point> squeezed = Bigger(100) & Select((Point p) => p.Pressure);
Set<Point> outliers = negative & SelectMany((Point p) => p.Channels);
Set<Sounding> deep = shallow & SelectMany((Sounding s) => s.Points);
Set<Sounding> failed = (squeezed & shallow | outliers) & SelectMany((Sounding s) => s.Points);
// reporting looks just good; and it is important as
// it is usually very volatile
Report("Job well done", soundings & !failed);
Report("To do", soundings & failed);
Report("Big upcoming expenses", soundings & failed & deep);
}
static void Report(string title, IEnumerable<object> args) =>
WriteLine($"{title}: {Join(",", args)}");
}
Where new library code is:
static class Selection
{
public static Selection<T, TResult> Select<T, TResult>(Func<T, TResult> selector) =>
new Selection<T, TResult>(selector);
public static Selection<T, TResult> SelectMany<T, TResult>(Func<T, IEnumerable<TResult>> selector) =>
new Selection<T, TResult>(selector);
}
// represents some kind of data set, so intersection operator looks ok
class Selection<T, TResult> {
public static Set<T> operator &(Set<TResult> left, Selection<T, TResult> right) =>
new Set<T>(r => left & right.Selector(r));
public static Set<T> operator &(Selection<T, TResult> left, Set<TResult> right) =>
new Set<T>(r => right & left.Selector(r));
public Selection(Func<T, TResult> selector)
{
Selector = (T i) => new[] { selector(i) };
}
public Selection(Func<T, IEnumerable<TResult>> selector)
{
Selector = selector;
}
Func<T, IEnumerable<TResult>> Selector { get; }
}
P.S. It is not for production yet, just some kind of working 2 am experiment :)