There is a full featured support of countable sets in .NET: IEnumerable<T>
. What about uncountable sets; sets defined by predicate? How can they be manipulated and interact with IEnumerable<T>
?
UPDATED: Countable and uncountable sets
Was: Condition-class.
Demo (you can play with it here online):
using static System.Console;
using static System.String;
class Program
{
static void Main(string[] args)
{
var NullOrEmpty = new Set<string>(string.IsNullOrEmpty);
var NullOrWhiteSpace = new Set<string>(string.IsNullOrWhiteSpace);
var WhiteSpace = NullOrWhiteSpace - NullOrEmpty;
WriteLine(WhiteSpace * " "); // True
var LowIncome = new Set<int>(i => i < 30000);
var HighIncome = new Set<int>(i => i > 140000);
var MiddleIncome = !LowIncome && !HighIncome;
var salaries = new[] { 25000, 40000, 35000, 80000, 65000, 120000, 200000 };
WriteLine(Join(",", salaries - MiddleIncome)); // 25000, 200000
}
}
Where full set of operations is defined as:
class Set<T>
{
public Set(Predicate<T> predicate)
{
Predicate = predicate;
}
public static bool operator *(Set<T> left, T right) =>
left.Predicate(right);
public static bool operator *(T left, Set<T> right) =>
right.Predicate(left);
public static Set<T> operator *(Set<T> left, Set<T> right) =>
new Set<T>(i => left.Predicate(i) && right.Predicate(i));
public static IEnumerable<T> operator *(Set<T> left, IEnumerable<T> right) =>
right.Where(i => left.Predicate(i));
public static IEnumerable<T> operator *(IEnumerable<T> left, Set<T> right) =>
left.Where(i => right.Predicate(i));
public static Set<T> operator +(Set<T> left, T right) =>
new Set<T>(i => left.Predicate(i) || right.Equals(i));
public static Set<T> operator +(T left, Set<T> right) =>
new Set<T>(i => left.Equals(i) || right.Predicate(i));
public static Set<T> operator +(Set<T> left, Set<T> right) =>
new Set<T>(i => left.Predicate(i) || right.Predicate(i));
public static Set<T> operator +(Set<T> left, IEnumerable<T> right) =>
new Set<T>(i => left.Predicate(i) || right.Contains(i));
public static Set<T> operator +(IEnumerable<T> left, Set<T> right) =>
new Set<T>(i => left.Contains(i) || right.Predicate(i));
public static Set<T> operator -(Set<T> left, T right) =>
new Set<T>(i => left.Predicate(i) && !right.Equals(i));
public static Set<T> operator -(T left, Set<T> right) =>
new Set<T>(i => left.Equals(i) && !right.Predicate(i));
public static Set<T> operator -(Set<T> left, Set<T> right) =>
new Set<T>(i => left.Predicate(i) && !right.Predicate(i));
public static Set<T> operator -(Set<T> left, IEnumerable<T> right) =>
new Set<T>(i => left.Predicate(i) && !right.Contains(i));
public static IEnumerable<T> operator -(IEnumerable<T> left, Set<T> right) =>
left.Where(i => !right.Predicate(i));
public static bool operator true(Set<T> x) => false;
public static bool operator false(Set<T> x) => false;
public static Set<T> operator |(Set<T> left, Set<T> right) =>
new Set<T>(v => left.Predicate(v) || right.Predicate(v));
public static Set<T> operator &(Set<T> left, Set<T> right) =>
new Set<T>(v => left.Predicate(v) && right.Predicate(v));
public static Set<T> operator !(Set<T> set) =>
new Set<T>(i => !set.Predicate(i));
Predicate<T> Predicate { get; }
}
Does this set of operations look mathematically correct?
2 Answers 2
Interesting try to use C#'s operators for set operations.
However, I think it is not a good extension in productive code because
- you have to learn the meaning of the operators first (and therefore check the implementation because operators are not descriptive).
- compared to methods, it is not possible to add comments to the operators
- the expressions are a little bit confusing to me. For example the '-' operator return different results depending on the position of the operands.
Further more, the functional features of C# provide already similar possibilities. For instance:
var salaries = new[] { 25000, 40000, 35000, 80000, 65000, 120000, 200000 };
var isLowIncome = new Func<int, bool>(i => i < 30000);
var isHighIncome = new Func<int, bool>(i => i > 140000);
var isMiddleIncome = new Func<int, bool>(i => !isLowIncome(i) && !isHighIncome(i));
Console.WriteLine(string.Join(",", salaries.Where(s => isLowIncome(s)))); // 25000, 200000
// or
Console.WriteLine(string.Join(",", salaries.Where(isLowIncome))); // 25000, 200000
That is readable and understandable for each C# developer without studing a framework.
-
\$\begingroup\$ As for me - code reduction is a very good reason to learn something :) All set operations look mathematically correct;
-
is noncommutative by definition. Actually, it is redundant - just a helper. \$\endgroup\$Dmitry Nogin– Dmitry Nogin2016年06月14日 15:31:15 +00:00Commented Jun 14, 2016 at 15:31
One more class is probably necessary here:
class Intersection<T> : IEnumerable<T>
{
public static readonly Intersection<T> Empty = new Intersection<T>();
public static implicit operator bool(Intersection<T> intersection) =>
intersection.Any();
public Intersection(params T[] enumerable)
{
Enumerable = enumerable;
}
public Intersection(IEnumerable<T> enumerable)
{
Enumerable = enumerable;
}
public IEnumerator<T> GetEnumerator() => Enumerable.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
IEnumerable<T> Enumerable { get; }
}
So set looks this way now (no changes to the demo Program code above):
class Set<T>
{
public Set(Predicate<T> predicate)
{
Predicate = predicate;
}
public static Intersection<T> operator *(Set<T> left, T right) =>
left.Predicate(right) ? new Intersection<T>(right) : Intersection<T>.Empty;
public static Intersection<T> operator *(T left, Set<T> right) =>
right.Predicate(left) ? new Intersection<T>(left) : Intersection<T>.Empty;
public static Set<T> operator *(Set<T> left, Set<T> right) =>
new Set<T>(i => left.Predicate(i) && right.Predicate(i));
public static Intersection<T> operator *(Set<T> left, IEnumerable<T> right) =>
new Intersection<T>(right.Where(i => left.Predicate(i)));
public static Intersection<T> operator *(IEnumerable<T> left, Set<T> right) =>
new Intersection<T>(left.Where(i => right.Predicate(i)));
public static Set<T> operator +(Set<T> left, T right) =>
new Set<T>(i => left.Predicate(i) || right.Equals(i));
public static Set<T> operator +(T left, Set<T> right) =>
new Set<T>(i => left.Equals(i) || right.Predicate(i));
public static Set<T> operator +(Set<T> left, Set<T> right) =>
new Set<T>(i => left.Predicate(i) || right.Predicate(i));
public static Set<T> operator +(Set<T> left, IEnumerable<T> right) =>
new Set<T>(i => left.Predicate(i) || right.Contains(i));
public static Set<T> operator +(IEnumerable<T> left, Set<T> right) =>
new Set<T>(i => left.Contains(i) || right.Predicate(i));
public static Set<T> operator -(Set<T> left, T right) =>
new Set<T>(i => left.Predicate(i) && !right.Equals(i));
public static Set<T> operator -(T left, Set<T> right) =>
new Set<T>(i => left.Equals(i) && !right.Predicate(i));
public static Set<T> operator -(Set<T> left, Set<T> right) =>
new Set<T>(i => left.Predicate(i) && !right.Predicate(i));
public static Set<T> operator -(Set<T> left, IEnumerable<T> right) =>
new Set<T>(i => left.Predicate(i) && !right.Contains(i));
public static Intersection<T> operator -(IEnumerable<T> left, Set<T> right) =>
new Intersection<T>(left.Where(i => !right.Predicate(i)));
public static bool operator true(Set<T> x) => false;
public static bool operator false(Set<T> x) => false;
public static Set<T> operator |(Set<T> left, Set<T> right) =>
new Set<T>(v => left.Predicate(v) || right.Predicate(v));
public static Set<T> operator &(Set<T> left, Set<T> right) =>
new Set<T>(v => left.Predicate(v) && right.Predicate(v));
public static Set<T> operator !(Set<T> set) =>
new Set<T>(i => !set.Predicate(i));
Predicate<T> Predicate { get; }
}
var WhiteSpace = NullOrWhiteSpace - NullOrEmpty;
:-D null - null = " " LOL magic \$\endgroup\$if(set)
? And why should that always be false? Also I don't see why you implement union and intersection using both the logical and arithmetical operations; can you say why you made this design decision? What does it buy the user, aside from confusion and errors in operator precedence? \$\endgroup\$