0

I've been given a simple (or not) task to perform and I'm wondering how I can realise the implementation in the most flexible way possible. So, suppose I'm given an interface

public interface IPerson
    {
        int GetId();
        string GetFirstName();
        string GetLastName();
        DateTime GetDateOfBirth();
        int GetHeight();

        //More methods can be added later...
    }

In short, there could be many properties added which can define an object Person. I've been asked to implement a custom collection named "PersonCollection" with the following rules: 1) It should utilize an "Add method" - adds the person object which is given as input. 2) (And this is the trickiest part) Remove - removes the person object with the maximum value and returns it. Now, what does it mean that a person has the maximum value? There may be many possible "algorithms" to determine that e.g. by first name/ last name/ height/ or any new properties of this interface that will be added in the future; you do not know in advance what algorithms may apply and which one should be used. Therefore, the implementation must be as flexible as possible by supporting all potential algorithms. For example: if the developer of Person interface somehow changes its properties, then this should still not affect your implementation of PersonCollection in any way! So, how can I make one "generic" method of finding the max value of any given property? Is it even possible?

This is a beginning of my implementation of the class:

public class PersonCollection : IEnumerable<IPerson>
    {
        private PersonCollection _pCollection;

        public PersonCollection(PersonCollection pCollection)
        {
            _pCollection = pCollection;
        }

        public async Task Add(IPerson person)
        {
            await _pCollection.Add(person);
        }


        public IEnumerator<IPerson> GetEnumerator()
        {
            return _pCollection.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

At first I thought of using Linq queries, but each query can access only one property using a lambda, so if someone will add a property to IPerson then my implementation will not cover it.

Any ideas or suggestions will be greatly appreciated Thank you

dextercom
  • 132
  • 2
  • 13
  • I'd try to implement it, but at first: what should your `_pCollection` _really_ be? something like `List<_pcollection>`? As it is now a `PersonCollection`, your `GetEnumerator()` method will lead to an infinite recursion only stopping with an exception at a nested instance with a `_pCollection` that is `null`. Same for your `Add()` method. – René Vogt Feb 12 '17 at 15:27
  • Well, in my mind it should be a custom generic collection behaving like a List. Thats why I thought to implement the IEnumerable interface – dextercom Feb 12 '17 at 15:31
  • Check out the ICollection interface. It's more in line with what your trying to do plus, it includes the IEnumerable interface as well. – John Stritenberger Feb 12 '17 at 15:48

3 Answers3

3

So, how can I make one "generic" method of finding the max value of any given property?

Let's begin by noticing that this is a more restricted version of your earlier requirement:

the implementation must be as flexible as possible by supporting all potential algorithms.

Do you see why those are different? Suppose the max algorithm I want is "the person with the longest last name, but if there is a tie, then the person with the shortest first name, and if there is a tie, then the heaviest person." That is not an algorithm that finds the maximum value of a particular property. It maximizes one value over the entire set, and then minimizes another over a subset, and so on.

Now at this point you should be realizing that your assignment is dangerously underspecified. You did not say what to do if there are zero maxima or two or more maxima. What if there is a tie? What if the collection is empty? You need to have precise rules for what happens in these scenarios.

The assignment is underspecified in another way. You say "all potential algorithms", but all potential algorithms for what exactly? Here, I've got an idea for a potential algorithm in my mind where Wendys is better than McDonalds, McDonalds is better than Burger King, Burger King is better than Wendys, and Red Lobster is better than all of them, and I am able to determine that Red Lobster is the winner. Does your algorithm need to deal with an intransitive betterness measure? This is not an academic point; the overload resolution algorithm in C# needs to be able to solve this maximization problem. (This was in fact one of my interview questions when I tried out for the C# team! Good times.)

To make it easier, let's crisp that up, a lot. We wish to find the maximum of a set given a comparison operator that produces a total order.

Now to answer your actual question:

At first I thought of using Linq queries, but each query can access only one property using a lambda,

Says who? What stops you from accessing multiple properties in a lambda?

You've come across the solution. LINQ also needs to be able to do arbitrary things to collections, and it does that by taking in a lambda.

What lambda?

You wish to find the maximum value over a set. So take in a delegate that takes two people and tells you which is the larger. I would suggest Comparison<T>.

https://msdn.microsoft.com/en-us/library/tfakywbh(v=vs.110).aspx

Then implement the standard maximization algorithm: assume the first is the maximum, run down your list until you find a larger one, keep going until you've found the largest.

Eric Lippert
  • 612,321
  • 166
  • 1,175
  • 2,033
1

Try a functional approach...

public IPerson RemoveMax(Func<IEnumerable<IPerson>, IPerson> maxFunction)
{
    IPerson person = maxFunction(this);
    Remove(person);
    return maxFunction(this);
}
public void Remove(IPerson person)
{
    //Person removal code...
}

Then you can pass in whatever function you need into RemoveMax in order to find the max person, as long as that function takes an IEnumerable as a parameter and returns an IPerson. You can code many different maxFunctions depending on the circumstance. A sample maxFunction being defined something like...

public IPerson MaxPerson(IEnumerable<IPerson> personCollection)
{
    IPerson maxPerson;
    foreach(IPerson person in personCollection)
        //Whatever to find max person based on properties...
    return maxPerson;
}
John Stritenberger
  • 992
  • 1
  • 10
  • 28
0

I suggest to use a List<Person> internally and implement IList<IPerson> or ICollection<IPerson> according to your needs. The implementation of these interface could be straight forwarding calls to the inner list:

public PersonCollection : IList<Person> // or ICollection<IPerson>
{
    private readonly List<IPerson> innerList = new List<IPerson>();

    // example implementation of IList.Add
    public void Add (IPerson person)
    {
         innerList.Add(person);
    }

    // implement the other interface methods accordingly

    // extend functionality with your methods 
    public async Task AddAsync(IPerson person) // note async naming convention
    {
        // your async add implementaion
    }

}

Now my suggestion for a RemoveByMaxOf method:

// as instance method of PersonCollection 
public Person RemoveByMaxOf<T>(Func<IPerson, T> propertySelector) where T : IComparable
{           
    if (Count == 0) return null;
    IPerson found = innerList0];
    T max = propertySelector(found);
    foreach (Person person in innerList.Skip(1))
    {
        T current = propertySelector(person);
        if (current.CompareTo(max) <= 0) continue;

        found = person;
        max = current;
    }

    innerList.Remove(found);
    return found;
}

So this takes a "selector": a delegate that projects the IPerson to the value of the property you want to compare.

To make it possible to find a maximum value, the property type must implement IComparable. There is no way to compare generic objects.

You can call this like that:

PersonCollection persons = ... // get your persons
IPerson removed = persons.RemoveByMaxOf(person => person.Name);

You can extend the method to take a custom comparer. This way the user of the class can decide what "maximum" really means (for example the alphabetic max of the Name or the length of the Name):

public Person RemoveByMaxOf<T>(Func<IPerson, T> propertySelector, Func<T, T, int> comparer)
{           
    if (Count == 0) return null;
    IPerson found = innerList0];
    T max = propertySelector(found);
    foreach (Person person in innerList.Skip(1))
    {
        T current = propertySelector(person);

        // use custom comparer
        if (comparer(current, max) <= 0) continue;

        found = person;
        max = current;
    }

    innerList.Remove(found);
    return found;
}

Now you can call it like:

IPerson removed = persons.RemoveByMaxOf(
                person => person.Name,
                (name1, name2) => name1.Length.CompareTo(name2.Length));

Additional notes: This only find the first element with the "maximum" value. You did not specify what should happen if there are more than one person having that maximum. And you might want to add some argument checking.

René Vogt
  • 40,163
  • 14
  • 65
  • 85
  • I would recommend against inheriting from `List`. Prefer composition to inheritance when creating collections. http://stackoverflow.com/questions/21692193/why-not-inherit-from-listt/21694054#21694054 – Eric Lippert Feb 12 '17 at 17:27
  • @EricLippert that's a very nice post you linked, I think I got your point. Removed that suggestion from my answer. – René Vogt Feb 12 '17 at 18:07