2
\$\begingroup\$

GitHub Repository

Previously: Countable and uncountable sets in .NET (clean version).

Thesis

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>?

Solution

Let;s introduce two library classes: Universe and Set, where Universe is a factory of Sets and Sets are defined by predicate, condition like Func<T, bool>.

Example:

using static Universe; 
Set<int> integers = Set<int>(); 
Set<int> zero = Set<int>(i => i == 0); 
Set<int> positive = Set<int>(i => i > 0); 

We define some basic calculus on sets:

Set<int> nonPositive = !positive;
Set<int> negative = !positive – zero;
Set<int> nonZero = positive | negative;

Intersection:

Set<int> liquidFreshWaterC = Universe.Set<int>(t => t > 0 && t < 100);
Set<int> liquidSaltWaterC = Universe.Set<int>(t => t >  -21.1 && t < 102);
Set<int> liquidWaterC =  liquidFreshWaterC & liquidSaltWaterC; // = 0 … 100

Now, how to test the set (it is just a combined condition underneath, nothing else) – let’s use intersection operator, as scalar value is just a set of one element:

bool isLiquidWater = liquidWaterC & 25; // = true

Union operator provides us with an another set:

Set<int> temperatures = liquidWaterC | 200; // 0 … 100, 200

The most useful feature is an integration with IEnumerable<T>. Let’s have:

Int[] tempC = new[] {-100, -10, 0, 10, 100, 200};

We can inersect them:

Enumerable<int> t = temperatures & tempC; // = 0, 10, 100, 200

Please note that non empty Enumerable<T> is truthy; an empty one is falsy:

bool nonEmpty = t; // true

We can join them, so result will be another Set<T>:

Set<T> joined = temperatures & tempC; // -100, -10, 0 … 100, 200

We can even exclude set from enumeration getting an enumeration, or exclude enumeration from set – getting set as a result.

Demo

Let's define Customer, Order, Invoice to calculate discounts (full solution is available online to play with):

class Customer
{
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
    public List<Invoice> Invoices { get; set; }
}

class Order
{
    public decimal Total { get; set; }
}

class Invoice
{
    public decimal Total { get; set; }
}

Now helpers:

static class Balances
{
    public static decimal Invoiced(this Customer customer) =>
        customer.Invoices.Sum(i => i.Total);

    public static decimal Ordered(this Customer customer) =>
        customer.Orders.Sum(o => o.Total);

    public static decimal Balance(this Customer customer) =>
        customer.Ordered() - customer.Invoiced();
}

Our discount rules are going to be:

 var loyal = Set<Customer>(с => с.Invoiced() > 10000);
 var debtors = Set<Customer>(c => c.Balance() > 0);
 var creditable = Set<Customer>(c => c.Balance() < 5000) & loyal;
 var bulk = Set<Customer>(c => c.Orders.Any(o => o.Total > 2000));

 // I think this syntax makes total sense
 var tenOff = bulk & loyal & !debtors;
 var fiveOff = bulk & loyal & creditable - tenOff;
 var noOff = !tenOff & !fiveOff;

Let’s test the sets:

foreach (var c in Repository.Customers & fiveOff)
    WriteLine($"-5%: {c.Name}");

foreach (var c in Repository.Customers & tenOff)
    WriteLine($"-10%: {c.Name}");

foreach (var c in Repository.Customers & noOff)
    WriteLine($"0%: {c.Name}");

Library code:

static class Universe
{
    public static Set<T> Set<T>() => Set<T>(i => true);
    public static Set<T> Set<T>(Predicate<T> predicate) => new Set<T>(predicate);
}

class Set<T>
{
    public Set(Predicate<T> predicate)
    {
        Predicate = predicate;
    }        

    public static Enumerable<T> operator &(Set<T> left, T right) =>
        left.Predicate(right) ? new Enumerable<T>(right) : Enumerable<T>.Empty;

    public static Enumerable<T> operator &(T left, Set<T> right) =>
        right.Predicate(left) ? new Enumerable<T>(left) : Enumerable<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 Enumerable<T> operator &(Set<T> left, IEnumerable<T> right) =>
        new Enumerable<T>(right.Where(i => left.Predicate(i)));

    public static Enumerable<T> operator &(IEnumerable<T> left, Set<T> right) =>
        new Enumerable<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 Enumerable<T> operator -(IEnumerable<T> left, Set<T> right) =>
        new Enumerable<T>(left.Where(i => !right.Predicate(i)));

    public static Set<T> operator !(Set<T> set) =>
        new Set<T>(i => !set.Predicate(i));

    Predicate <T> Predicate { get; }
}

class Enumerable<T> : IEnumerable<T>
{
    public static readonly Enumerable<T> Empty = new Enumerable<T>();
    public static implicit operator bool(Enumerable<T> intersection) => intersection.Any();

    public Enumerable(params T[] items)
    {
        Items = items;
    }

    public Enumerable(IEnumerable<T> items)
    {
        Items = items;
    }

    public IEnumerator<T> GetEnumerator() => Items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    IEnumerable<T> Items { get; }
}
\$\endgroup\$
  • \$\begingroup\$ Could you add a short summary of the operators and it's operating mode. I am a little bit confused because the same expression temperatures & tempC;results in an object of typ Enumerable<int> first, and than in an object of type Set<T> \$\endgroup\$ – JanDotNet Jun 16 '16 at 14:46
  • \$\begingroup\$ @JanDotNet Here it is, please see below. I have one subtle place in the API. Probably it should be Enumerable<T>... \$\endgroup\$ – Dmitry Nogin Jun 16 '16 at 15:18
  • 4
    \$\begingroup\$ Even if syntax may seem clear and terse I still have doubts about operator overloading. If you can add standard set operators (with exactly same usage in all cases) then you're welcome but if you reuse existing operators (with different meanings) and you also add corner cases and exceptions then...why??? Please consider how they implemented sets in C++ (cplusplus.com/reference/set/set) and discussion about operator overloading (stackoverflow.com/questions/4421706/operator-overloading). \$\endgroup\$ – Adriano Repetti Jun 16 '16 at 15:50
  • \$\begingroup\$ Yep, you are right. What do you think about this updated answer below? \$\endgroup\$ – Dmitry Nogin Jun 16 '16 at 16:55
  • \$\begingroup\$ Even if your experiments are considered controversial I like them. I like experimentation in general so from me definetely +1 because they change the point of view to several well established schemas. Thinking outside the box is a good thing ;-) \$\endgroup\$ – t3chb0t Jun 16 '16 at 17:20
1
\$\begingroup\$

REVISED VERSION

GitHub Repository

==========================================================================

Previously: Countable and uncountable sets in .NET (clean version).

Thesis 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>?

What is new? Operators has been unified:

1) Implicit conversion defined:

  • from IEnumerable<T> to Set<T> => Set<T>(i => source.Contains(i))
  • from T value to Set<T> => Set<T>(i == value)

2) Operators redefined to ensure symmetry and simplicity:

   // tests an element,
   // we can iterate over result or implicitly cast it to Boolean.
   Set<T>[T value] => Enumerable<T> 
   Set<T>[IEnumerable<T> sequence] => Enumerable<T> 

   // Set manipulations
   Set<T> & Set<T> => Set<T>
   Set<T> | Set<T> => Set<T>
   Set<T> - Set<T> => Set<T> // redundant, but so, so useful 
   !Set<T> => Set<T>

Solution Let’s introduce two library classes: Universe and Set, where Universe is a factory of Sets and Sets are defined by predicate, condition like Func<T, bool>. Example:

using static Universe; 
Set<int> integers = Universe.Set<int>(); 
Set<int> zero = Universe.Set<int>(i => i == 0); 
Set<int> positive = Universe.Set<int>(i => i > 0); 

We define some basic calculus on sets:

Set<int> nonPositive = !positive;
Set<int> negative = !positive – zero;
Set<int> nonZero = positive | negative;

Intersection:

Set<int> liquidFreshWaterC = Universe.Set<int>(t => t > 0 && t < 100);
Set<int> liquidSaltWaterC = Universe.Set<int>(t => t >  -21.1 && t < 102);
Set<int> liquidWaterC =  liquidFreshWaterC & liquidSaltWaterC; // = 0 … 100

Now, how to test the set (it is just a combined condition underneath, nothing else) – let’s use intersection operator, as scalar value is just a set of one element:

bool isLiquidWater = liquidWaterC[25]; // = true 

Actually, tests return Enumerable<T>, which is truthy; it could be falsy if empty. We can iterate the result, getting 0 or 1 element.

Union operator provides us with an another set:

Set<int> temperatures = liquidWaterC | 200; // 0 … 100, 200

The most useful feature is an integration with IEnumerable<T>. Let’s have:

Int[] tempC = new[] {-100, -10, 0, 10, 100, 200};

We can inersect them:

Set<int> t = temperatures & tempC; // = 0, 10, 100, 200

We can join them, so result will be another Set<T>:

Set<T> joined = temperatures & tempC; // -100, -10, 0 … 100, 200

We can even exclude set from enumeration getting an enumeration, or exclude enumeration from set – getting set as a result.

Demo Let’s define Customer, Order, Invoice to calculate discounts (full solution is available online to play with):

class Customer
{
    public string Name { get; set; }
    public List<Order> Orders { get; set; }
    public List<Invoice> Invoices { get; set; }
}

class Order
{
    public decimal Total { get; set; }
}

class Invoice
{
    public decimal Total { get; set; }
}

Now helpers:

static class Balances
{
    public static decimal Invoiced(this Customer customer) =>
        customer.Invoices.Sum(i => i.Total);

    public static decimal Ordered(this Customer customer) =>
        customer.Orders.Sum(o => o.Total);

    public static decimal Balance(this Customer customer) =>
        customer.Ordered() - customer.Invoiced();
}

Our discount rules are going to be:

 var loyal = Set<Customer>(с => с.Invoiced() > 10000);
 var debtors = Set<Customer>(c => c.Balance() > 0);
 var creditable = Set<Customer>(c => c.Balance() < 5000) & loyal;
 var bulk = Set<Customer>(c => c.Orders.Any(o => o.Total > 2000));
 var tenOff = bulk & loyal & !debtors;
 var fiveOff = bulk & loyal & creditable - tenOff;
 var noOff = !tenOff & !fiveOff;

Let’s test the sets:

   foreach (var c in Repository.Customers.Intersect(fiveOff))
       WriteLine($"-5%: {c.Name}");

   foreach (var c in Repository.Customers.Intersect(tenOff))
       WriteLine($"-10%: {c.Name}");

   foreach (var c in Repository.Customers.Intersect(noOff))
       WriteLine($"0%: {c.Name}");

Library code:

static class Universe
{
    public static Set<T> Set<T>() => Set<T>(i => true);
    public static Set<T> Set<T>(Predicate<T> predicate) => new Set<T>(predicate);
    public static Enumerable<T> Intersect<T>(this IEnumerable<T> source, Set<T> set) => 
        set[source];
}

class Set<T>
{
    public static implicit operator Set<T>(T value) => 
        new Set<T>(value);

    public Set(params T[] values)
        : this(values.AsEnumerable())
    {
    }

    public Set(IEnumerable<T> values)
        : this(i => values.Contains(i))
    {
    }

    public Set(Predicate<T> predicate)
    {
        Predicate = predicate;
    }

    public Enumerable<T> this[T value] =>
        Predicate(value) ? new Enumerable<T>(value) : Enumerable<T>.Empty;

    public Enumerable<T> this[IEnumerable<T> values] =>
        new Enumerable<T>(values.Where(i => 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) =>
        left & new Set<T>(right);

    public static Set<T> operator &(IEnumerable<T> left, Set<T> right) =>
        new Set<T>(left) & right;

    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) =>
        left | new Set<T>(right);

    public static Set<T> operator |(IEnumerable<T> left, Set<T> right) =>
        new Set<T>(left) | right;

    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) =>
        left - new Set<T>(right);

    public static Set<T> operator -(IEnumerable<T> left, Set<T> right) =>
        new Set<T>(left) - right;

    public static Set<T> operator !(Set<T> set) =>
        new Set<T>(i => !set.Predicate(i));

    Predicate <T> Predicate { get; }
}

class Enumerable<T> : IEnumerable<T>
{
    public static readonly Enumerable<T> Empty = new Enumerable<T>();
    public static implicit operator bool(Enumerable<T> intersection) => intersection.Any();

    public Enumerable(params T[] items)
    {
        Items = items;
    }

    public Enumerable(IEnumerable<T> items)
    {
        Items = items;
    }

    public IEnumerator<T> GetEnumerator() => Items.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    IEnumerable<T> Items { 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.