vWorker link: C# Algorithm, Task Levels
Competition start date: 2012.05.26
Competition end date:
Download associated files. (Version 2.1)
Please make sure to read the Welcome page for general competition guidelines.
If you would like to participate in this competition please register an account on vWorker.com
Update:
2012.05.29 - See the bottom of this post.2012.05.30 - Just want to make it clear that the sequence must be preserved amongst a task type.
2012.05.31 - The last task in the input collection will never change.
2012.06.08 - The winning code can be found here.
Introduction:
In the process industry we often have to deal with tasks. A task can be either of two types. Either "producing" (i.e has a positive quantity), or "consuming" (i.e. has a negative quantity). A task also has a date range associated with it. (i.e. a task will start and finish at pre defined times.)When we have a collection of tasks, we want to be able to keep track of the running quantities being produced or consumed.
The purpose of this competition is two fold:
- First you will have to implement an algorithm that determines whether a collection of tasks will stay within a certain range or not.
- If the level breaches the range, you have to return a collection of tasks that does not breach the range.
An illustration is shown below:
Requirements:
Write the code for:
- WillBreachMinLevel()
- WillBreachMaxLevel()
- TryToMakeTasksComply()
The first two are relatively easy the last method is more interesting.
The stubs for these functions are found in the TaskCollectionExtensions.cs file.
The unit tests are defined in TaskCollectionExtensionTest.cs
Your tasks is to write the code to pass all tests defined in the file mentioned above. There are also a few secret tests to differentiate between competitors.
Assumptions:
- A collection of tasks can have multiple producing tasks and multiple consuming tasks.Constraints:
- If a collection of tasks will go above the MAX level, only the producing tasks will move forward to try and keep the levels within the MIN-MAX range at all times. (The consuming tasks will stay where they are.)- If a collection of tasks will go below the MIN level, only the consuming tasks will move forward to try and keep the levels within the MIN-MAX range at all times. (The producing tasks will stay where they are.)
- TryToMakeTasksComply() will return null if no suitable times can be found for the tasks so that the running level always stays in the range.
If a solution exists, the method will return a collection such that the running level always stays in the range and the sum of the starting times of all tasks is minimised. (i.e. only move a producing or consuming task forward to fit in the range ... but not more than is necessary.)
Hints:
The classes Task, DateRange and their extensions may have useful methods. (see the "Extensions" folder in the project.)Notes:
If you have any questions, please post them as comments to this post. Please do not ask us questions on vWorker, as we do not have time to answer all of them individually.
The downloadable files also include a project called TaskLevelPlotter which may assist you in visualising the running levels in a given collection. (You need to have MSChart installed for .NET 3.5)
A picture is show below:
Finally, please check this post regularly as updates will be posted on this blog post, or in the comments.
All the best!
===============================================
2012.05.29
This is a response to Egor's comments below. Using the data he provided we obtain the following plot:
The solution the algorithm should provide is shown below:
The data for the correct collection is:
correctCollection.Add(task1.ChangeTimes(t5));
correctCollection.Add(task2.ChangeTimes(t6));
correctCollection.Add(task3.ChangeTimes(t7));
correctCollection.Add(task4);
correctCollection.Add(task5);
correctCollection.Add(task6);
correctCollection.Add(task7);
This comment has been removed by the author.
ReplyDeleteTests
ReplyDeleteWillBreachMaxLevel_ProducingTasksWithEndValueEqualToMaxLevel_ReturnsFalse()
and
WillBreachMaxLevel_ProducingTasksWithEndValueGreaterThanMaxLevel_ReturnsTrue()
have bugs, .WillBreachMinLevel() is used instead of .WillBreachMaxLevel().
Egor is right.
ReplyDeleteAND THERE IS A PROBLEM WITH OTHER TESTS ALSO
1) THIS TEST : TryToMakeTasksComply_WillGoBelowMinValue_ReturnsCorrectCollection2()
Was assuming that the OrderBy method will order by startdate only
change :
Assert.True(producingTasks[0].Equals(task4));
Assert.True(producingTasks[1].Equals(task2));
Assert.True(producingTasks[2].Equals(task3));
bacame:
Assert.True(producingTasks[0].Equals(task2));
Assert.True(producingTasks[1].Equals(task4));
Assert.True(producingTasks[2].Equals(task3));
Also it happenes in other functions
ALSO THIS LINE IN ALL FUNCTIONS IS WRONG
var producingTasks = newCollection.OrderBy(x => x.DateRange).Select(task => task.TaskType == TaskType.Produce).ToList();
BECAUSE IT WILL SELECT BOOLEAN VALUES, IT SHOULD BE:
var producingTasks = newCollection.OrderBy(x => x.DateRange).Where(task => task.TaskType == TaskType.Produce).ToList();
I have uploaded the submission on vWorker and fixed all the tests
Thanks for spotting the problems.
ReplyDeleteThe tests should be accurate now. Please see the top of this blog post for the link to the updated files. (They are hosted on Google Drive, since vWorker does not allow us to change files once a competition has started.)
@Moustafa: You are correct, the order of the tasks in TryToMakeTasksComply_WillGoBelowMinValue_ReturnsCorrectCollection2() is wrong. The main Assertion we wanted to capture is that those tasks property values must not change.
Thanks for updating, but still error exists in
ReplyDeleteTryToMakeTasksComply_WillGoAboveMaxValue_ReturnsCorrectCollection3
var consumingTasks = newCollection.Select(task => task.TaskType == TaskType.Consume).ToList();
Should be
var consumingTasks = newCollection.Where(task => task.TaskType == TaskType.Consume).ToList();
Using Where instead of Select
@Moustafa: Thanks. All fixed now.
ReplyDeleteI'm pretty sure you guys need to go more into genetic algorithms instead of trying to solve it in a conventional procedural way.
ReplyDelete@Egor: Did you try making up some data to test on your own? I will let the other guys comment first if they want to, and then make a few suggestions tomorrow.
ReplyDelete@orc: I didn't, but I'm following the contest. There always can be a difficult enough test case that contains several solutions, some are obvious, some are beter fitting given constraints. Plain algorithmic top-bottom traversal will not be able to determine between those and pick the best-fitting one. Heuristics will.
ReplyDeleteThis task is described here, it's close to NP-hard 'job-shop scheduling problem':
http://en.wikipedia.org/wiki/Genetic_algorithm_scheduling
I can be wrong though, who knows. Just my point of view.
@Egor: The problem is not as simple as it first looks. However it is not NP hard. The key is tackling the problem from the correct angle.
DeleteHere's a test that requires more than plain top-bottom traversal to fit all the declared constraints:
ReplyDeletedouble minLevel = 100;
double maxLevel = 200;
double initialLevel = 150;
var taskCollection = new Collection();
DateTime t1 = DateTime.Now;
DateTime t2 = t1.AddHours(1);
DateTime t3 = t1.AddHours(2);
DateTime t4 = t1.AddHours(3);
DateTime t5 = t1.AddHours(4);
DateTime t6 = t1.AddHours(5);
DateTime t7 = t1.AddHours(6);
DateTime t8 = t1.AddHours(7);
DateTime t9 = t1.AddHours(8);
Task task1 = Task.CreateUsingQuantity(t2, t3, -150);
Task task2 = Task.CreateUsingQuantity(t2.AddHours(0.5), t3.AddHours(0.5), -100);
Task task3 = Task.CreateUsingQuantity(t3, t4, -50);
Task task4 = Task.CreateUsingQuantity(t4, t5, 50);
Task task5 = Task.CreateUsingQuantity(t5, t6, 50);
Task task6 = Task.CreateUsingQuantity(t5, t7, 100);
Task task7 = Task.CreateUsingQuantity(t7, t8, 150);
taskCollection.Add(task1);
taskCollection.Add(task2);
taskCollection.Add(task3);
taskCollection.Add(task4);
taskCollection.Add(task5);
taskCollection.Add(task6);
taskCollection.Add(task7);
// Act
IEnumerable newCollection = taskCollection.TryToMakeTasksComply(minLevel, maxLevel, initialLevel);
var correctCollection = new Collection();
Task task11 = Task.CreateUsingQuantity(t6.AddHours(0.35), t7.AddHours(0.35), -150);
Task task21 = Task.CreateUsingQuantity(t4.AddHours(0.5), t5.AddHours(0.5), -100);
Task task31 = Task.CreateUsingQuantity(t4, t5, -50);
Task task41 = Task.CreateUsingQuantity(t4, t5, 50);
Task task51 = Task.CreateUsingQuantity(t5, t6, 50);
Task task61 = Task.CreateUsingQuantity(t5, t7, 100);
Task task71 = Task.CreateUsingQuantity(t7, t8, 150);
correctCollection.Add(task11);
correctCollection.Add(task21);
correctCollection.Add(task31);
correctCollection.Add(task41);
correctCollection.Add(task51);
correctCollection.Add(task61);
correctCollection.Add(task71);
It has several solutions.
correctCollection was created manually according to temporal constraint "only move a producing or consuming task forward to fit in the range ... but not more than is necessary". It moves (-50) task by 1 hour, (-100) task by 2 hours, (-150) task by 4.35 hours totaling to average 2.45 hr shift.
Can we use 'amount of average task shift' as one of indicators to measure how good algorithm performed?
I have updated the main post with the solution. The sequence of the consuming jobs has to be preserved.
Delete"The sequence of the consuming jobs has to be preserved."
DeleteThis was the missing constraint. Now it looks much more like top-down traversal task.
Question...
ReplyDeleteWhy in test
TryToMakeTasksComply_WillGoAboveMaxValue_ReturnsCorrectCollection()
you are moving task 1.5 hours forward to achieve 50% overlap?
And in test
TryToMakeTasksComply_WillGoBelowMinValue_ReturnsCorrectCollection()
you are moving it to exactly match the other task (100% overlap)?
I think both tasks can have 100% overlap, or am I just being stupid?
*both tests
Delete@Egor: If in doubt, please use the TaskLevelPlotter library to check the results. Both tests are correct.
DeleteYeah, already noticed that "fit in range" thing. Requirements are scattered across whole page, hard to find all.
DeleteCan you define more precisely what you mean by "the sequence of the consuming tasks"? I.e. does the sequence refer to the start time of the tasks, or both the start and the end times? E.g. can you push one task back so that its end time now overlaps the start time of the next task, or does that change the sequence?
ReplyDeleteSequence only refers to the order of the tasks, so we are only interested in their start times.
DeleteYou should never have to push a task back in time. A task can only be moved forward. (But you shouldn't move a task forward more than it needs to.)
Overlaps between tasks is acceptable in this competition.
If you don't want coders to see what others have coded and your discussion with them, you'll need to set "Co-contestant visibility" to 'Private' next time.
ReplyDeleteOK thanks. I will know for next time. Cheers.
DeleteAnother question about sequencing: If two tasks start at the same time, presumably that means if one is moved forward, both have to be moved together. But, if you move one task forward until its start time becomes the same as the next task's start time, does that violate the sequence?
ReplyDeleteAlso what does the new requirement mean, about how the last "input task" will never change? What is an input task?
Thanks,
Neil
If two tasks are of the same type (either consuming or producing), they will be moved together. However if one is producing and the other consuming, it will depend on whether the MIN or MAX level has been breached. If the MIN level was breached then the CONSUMING task will move forward, to give more time to the producing task to produce more before the consuming task starts. If the MAX level is breached then the PRODUCING task will move forward.
DeleteIf you move multiple tasks such that they eventually all start at the same time, that is fine. This will not violate the sequence. (for this competition anyway. In real life you also have to check what resources the tasks are being carried out on, since most of the time one resource can only do one task at a time... but that is beyond the scope of this competition.)
Concerning the "input task": I think I was referring to the last task in the input collection. i.e. The last task in a collection should never have to change its start time.
Who won?
ReplyDelete