score:1

Accepted answer

The idea is choosing first element and then removing corresponding elements from list to make sure next choice is unique, as below:

var rankings = new List<Rankings> {
    new Rankings{  JOB_ID= 4,EMPLOYEE_ID= 3, OVERALL_SCORE=  800 },
    new Rankings{  JOB_ID= 4,EMPLOYEE_ID= 4, OVERALL_SCORE=  800 },
    new Rankings{  JOB_ID= 3,EMPLOYEE_ID= 1, OVERALL_SCORE=  800 },
    new Rankings{  JOB_ID= 3,EMPLOYEE_ID= 2, OVERALL_SCORE= 1200 },
    new Rankings{  JOB_ID= 2,EMPLOYEE_ID= 1, OVERALL_SCORE= 1600 },
    new Rankings{  JOB_ID= 2,EMPLOYEE_ID= 2, OVERALL_SCORE= 1800 },
    new Rankings{  JOB_ID= 4,EMPLOYEE_ID= 1, OVERALL_SCORE= 2000 },
    new Rankings{  JOB_ID= 4,EMPLOYEE_ID= 2, OVERALL_SCORE= 2100 },
    new Rankings{  JOB_ID= 1,EMPLOYEE_ID= 1, OVERALL_SCORE= 6400 },
};
var cpy = new List<Rankings>(rankings);
var result = new List<Rankings>();
while (cpy.Count() > 0)
{
    var first = cpy.First();
    result.Add(first);
    cpy.RemoveAll(r => r.EMPLOYEE_ID == first.EMPLOYEE_ID || r.JOB_ID == first.JOB_ID);
}

result:

+--------+-------------+---------------+
| JOB_ID | EMPLOYEE_ID | OVERALL_SCORE | 
+--------+-------------+---------------+
|      4 |           3 |           800 |
|      3 |           1 |           800 |   
|      2 |           2 |          1800 |
+--------+-------------+---------------+

score:0

Basically you could use System.Linq.Distinct method reinforced with the custom equality comparer IEqualityComparer<Ranking>. The System.Linq provide this method out of the box.

public class Comparer : IEqualityComparer<Ranking>
{
    public bool Equals(Ranking l, Ranking r)
    {
        return l.JOB_ID == r.JOB_ID || l.EMPLOYEE_ID == r.EMPLOYEE_ID;
    }

    public int GetHashCode(Ranking obj)
    {
        return 1;
    }
}

The trick here is with the GetHashCode method, and then as simple as this

rankings.Distinct(new Comparer())

score:1

Really, if you're trying to get the best score for the job, you don't need to select by unique JOB_ID/EMPLOYEE_ID, you need to sort by JOB_ID/OVERALL_SCORE, and pick out the first matching employee per JOB_ID (that's not already in the "assigned list").

You could get the items in order using LINQ:

var sorted = new List<Ranking>
( 
  rankings
    .OrderBy( r => r.JOB_ID )
    .ThenBy( r => r.OVERALL_SCORE ) 
);

...and then peel off the employees you want...

  var best = new List<Ranking>( );
  sorted.ForEach( r1 => 
  {
    if ( !best.Any
    ( 
      r2 => 
        r1.JOB_ID == r2.JOB_ID 
        || 
        r1.EMPLOYEE_ID == r2.EMPLOYEE_ID
    ) )
    {
      best.Add( r1 );
    }
  } );

Instead of using Linq to produce a sorted list, you could implement IComparable<Ranking> on Ranking and then just sort your rankings:

public class Ranking : IComparable<Ranking>
{
  int IComparable<Ranking>.CompareTo( Ranking other )
  {
    var jobFirst = this.JOB_ID.CompareTo( other.JOB_ID );
    return
      jobFirst == 0?
        this.OVERALL_SCORE.CompareTo( other.OVERALL_SCORE ):
        jobFirst;
  } 

  //--> other stuff...

}

Then, when you Sort() the Rankings, they'll be in JOB_ID/OVERALL_SCORE order. Implementing IComparable<Ranking> is probably faster and uses less memory.

Note that you have issues...maybe an unstated objective. Is it more important to fill the most jobs...or is more important to find work for the most employees? The route I took does what you suggest, and just take the best employee for the job as you go...but, maybe, the only employee for job 2 may be the same as the best employee for job 1...and if you put him/her on job 1, you might not have anybody left for job 2. It could get complicated :-)


Related Articles