Here are the test cases (this is all C# if that isn't immediately obvious):
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using AlgorithmHarness.Optimization;
namespace OptimizationTests
{
[TestClass]
public class BabysitterTests
{
private static BabySitter scheduler = new BabySitter();
private static List<Candidate> result = new List<Candidate>();
[TestMethod]
public void ConstructorNonNull()
{
Assert.IsNotNull(scheduler);
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void NullCandidateListShouldThrowException()
{
result.Clear();
scheduler.Schedule(new DateTime(1), new DateTime(10), null, result);
}
[TestMethod]
[ExpectedException(typeof(ArgumentOutOfRangeException))]
public void StartAfterFinishShouldThrowExectption()
{
List<Candidate> candidates = new List<Candidate>();
result.Clear();
scheduler.Schedule(new DateTime(10), new DateTime(1), candidates, result);
}
[TestMethod]
public void EmptyListReturnsNoSolution()
{
List<Candidate> candidates = new List<Candidate>();
result.Clear();
Assert.AreEqual(false, scheduler.Schedule(new DateTime(1), new DateTime(10), candidates, result));
}
[TestMethod]
public void ScheduleSingleInput()
{
List<Candidate> candidates = new List<Candidate>()
{
new Candidate(1, new DateTime(1), new DateTime(10))
};
result.Clear();
Assert.AreEqual(true, scheduler.Schedule(new DateTime(2), new DateTime(9), candidates, result));
ValidateCoverage(new DateTime(2), new DateTime(9), result, "Step 1");
// shouldn't find a solution
result.Clear();
Assert.AreEqual(false, scheduler.Schedule(new DateTime(1), new DateTime(11), candidates, result));
}
[TestMethod]
public void ScheduleSingleResult()
{
List<Candidate> candidates = new List<Candidate>()
{
new Candidate(1, new DateTime(1), new DateTime(2)),
new Candidate(2, new DateTime(2), new DateTime(5)),
new Candidate(3, new DateTime(2), new DateTime(7)),
new Candidate(4, new DateTime(3), new DateTime(12)),
new Candidate(5, new DateTime(4), new DateTime(8)),
new Candidate(6, new DateTime(9), new DateTime(15)),
new Candidate(7, new DateTime(16), new DateTime(20))
};
result.Clear();
Assert.AreEqual(true, scheduler.Schedule(new DateTime(16), new DateTime(18), candidates, result));
ValidateCoverage(new DateTime(16), new DateTime(18), result, "Step 1");
Assert.AreEqual(1, result.Count, "Step 1 count");
result.Clear();
Assert.AreEqual(true, scheduler.Schedule(new DateTime(4), new DateTime(12), candidates, result));
ValidateCoverage(new DateTime(4), new DateTime(12), result, "Step 2");
Assert.AreEqual(1, result.Count, "Step 2 count");
// no solution
result.Clear();
Assert.AreEqual(false, scheduler.Schedule(new DateTime(3), new DateTime(18), candidates, result), "Step 3");
result.Clear();
Assert.AreEqual(false, scheduler.Schedule(new DateTime(3), new DateTime(25), candidates, result), "Step 4");
}
[TestMethod]
public void ScheduleSitters()
{
List<Candidate> candidates = new List<Candidate>()
{
new Candidate(1, new DateTime(1), new DateTime(2)),
new Candidate(2, new DateTime(2), new DateTime(5)),
new Candidate(3, new DateTime(2), new DateTime(7)),
new Candidate(4, new DateTime(3), new DateTime(12)),
new Candidate(5, new DateTime(4), new DateTime(8)),
new Candidate(6, new DateTime(9), new DateTime(15)),
new Candidate(7, new DateTime(16), new DateTime(20))
};
result.Clear();
Assert.AreEqual(true, scheduler.Schedule(new DateTime(1), new DateTime(10), candidates, result));
ValidateCoverage(new DateTime(1), new DateTime(10), result, "Step 1");
Assert.AreEqual(3, result.Count, "Step 1 count");
result.Clear();
Assert.AreEqual(true, scheduler.Schedule(new DateTime(1), new DateTime(12), candidates, result));
ValidateCoverage(new DateTime(1), new DateTime(12), result, "Step 2");
Assert.AreEqual(3, result.Count, "Step 2 count");
result.Clear();
Assert.AreEqual(true, scheduler.Schedule(new DateTime(1), new DateTime(15), candidates, result));
ValidateCoverage(new DateTime(1), new DateTime(15), result, "Step 3");
Assert.AreEqual(4, result.Count, "Step 3 count");
}
private void ValidateCoverage(DateTime start, DateTime finish, List<Candidate> candidates, string test)
{
Assert.IsNotNull(candidates, test + ": Null return");
foreach (Candidate candidate in candidates)
{
Assert.AreEqual(true, candidate.Start <= start, test + " start: " + start.Ticks + "Id: " + candidate.Id);
start = candidate.Finish;
if (start >= finish) return;
}
Assert.Fail(test + " failed to cover finish");
}
}
}
and here's the resulting algorithm:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AlgorithmHarness.Optimization
{
public class Candidate
{
public Candidate(int id, DateTime start, DateTime finish)
{
Id = id;
Start = start;
Finish = finish;
}
public int Id { get; set; }
public DateTime Start { get; set; }
public DateTime Finish { get; set; }
}
public class BabySitter
{
public BabySitter() { }
public bool Schedule(DateTime start, DateTime finish, List<Candidate> candidates, List<Candidate> result)
{
if ((null == candidates) || (null == start) || (null == finish)) throw new ArgumentNullException();
if (start >= finish) throw new ArgumentOutOfRangeException();
if (candidates.Count == 0) return false;
Candidate best = null;
List<Candidate> next = new List<Candidate>();
foreach (Candidate candidate in candidates)
{
if (candidate.Start <= start)
{
if (candidate.Finish >= finish)
{ // this one can cover the remaining time
// so that's good enough
result.Add(candidate);
return true;
}
else if ((best == null) || (candidate.Finish > best.Finish))
// best we've found so far
best = candidate;
//else - they can't go as long as one we've already got,
// so there's no reason to consider them further
}
else
// this one can't start early enough to use now,
// but might work out later
next.Add(candidate);
}
if (best == null) return false; // nobody available at this start time
// try to find an optimal solution for the remaining time
// using just the sitters with later start times
result.Add(best);
return Schedule(best.Finish, finish, next, result);
}
}
}
It looks like more than it is because of the exception trapping and comments. The actual algorithm is a mere 11 lines of code.
No comments:
Post a Comment