3
\$\begingroup\$

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?

\$\endgroup\$
  • 5
    \$\begingroup\$ This is mind blowing var WhiteSpace = NullOrWhiteSpace - NullOrEmpty; :-D null - null = " " LOL magic \$\endgroup\$ – t3chb0t Jun 14 '16 at 14:02
  • 3
    \$\begingroup\$ Can you explain your implementations of operators true and false? These don't make any sense to me. Under what circumstances would you want to say 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\$ – Eric Lippert Jun 14 '16 at 16:48
  • \$\begingroup\$ @EricLippert Sorry, I just got it. Some retired garbage survived - here you can find a clean version: Countable and uncountable sets 4 specializations on Coursera + Job + codereview = 4 hours of sleep :) \$\endgroup\$ – Dmitry Nogin Jun 14 '16 at 20:22
4
\$\begingroup\$

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.

\$\endgroup\$
  • \$\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 Jun 14 '16 at 15:31
0
\$\begingroup\$

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; }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.