Optimum movie schedule considering duration and no overlap - algorithm

I am struggling to find a good strategy to tackle this problem. I considered sorting the collection of movies, by duration, but that seems to obscure the overlap condition. Any ideas?
Another thing I was thinking about was sorting based on start month and then, approaching as an extension of this problem.
An actor is offered a set of movies for a certain (start month and
duration). You have to select the movies the actor should do so that
the actor can do the most number of movies in the year, without any
overlaps.
It is assumed that movies start on the first of a month, and end on
the last day. Months are of 30 days (should not be required?).

I wrote up some code to demonstrate a solution. The overall essence is similar to what Edward Peters mentioned. First thing is to sort the movies by their end times. Now, greedily the first option is always chosen.
Why does choosing the first option work?
In our case, our ordering A had the first movie, some a. Say there is another ordering B where the first movie is some b, such that b ≠ a. There must be an ordering (we already know about the existence of A) such that, A = {B – {b}} U {a}. Obviously, in the case of B, b would have the smallest time; since b ≠ a, duration(b) >= duration(a).
public class Optimum_Movie_Schedule {
public static void main(String[] args) {
Movie[] movies = new Movie[] {
new Movie("f", 5, 9),
new Movie("a", 1, 2),
new Movie("b", 3, 4),
new Movie("c", 0, 6),
new Movie("d", 5, 7),
new Movie("e", 8, 9)
};
// sort by endMonth; startMonth if endMonth clash
Arrays.sort(movies);
System.out.println(Arrays.toString(movies));
System.out.println("Final list: ");
System.out.print(movies[0] + " ");
int cur = 0;
for(int i = 1 ; i < movies.length ; i++) {
if(movies[i].startMonth > movies[cur].endMonth) {
System.out.print(movies[i] + " ");
cur = i;
}
}
}
static class Movie implements Comparable<Movie> {
String name;
int startMonth;
int endMonth;
int duration;
public Movie(String name, int start, int end) {
this.name = name;
this.startMonth = start;
this.endMonth = end;
this.duration = (end + 1) - start;
}
public int compareTo(Movie o) {
if(this.endMonth < o.endMonth)
return -1;
if(this.endMonth == o.endMonth)
if(this.startMonth < o.startMonth)
return -1;
return 1;
}
public String toString() {
return name + "("+ startMonth + ", " + endMonth + ")";
}
}
}

Take your list of movies, sorted by their end dates. For each movie, you want to solve "What are the maximum number of movies I could have acted in, by the end of this movie." This can be found recursively as the maximum of either the result for the most recent movie, or one plus the result for the most recent movie to end before this one began.
The first case corresponds to "I did not act in this movie, so would have been able to pursue other movies during that duration." The second case corresponds to "I acted in this movie, but was not available for any movies that did not end before this movie began."

Related

CodinGame: Let's Go To The Cinema

It made already some months, I try to resolve the codingame "Let's Go To The Cinema" of the CodinGame platform, a problem of mid-level difficulty, but I can't still figure out the solution around.
The codingame lays on three concepts of recursion, simulation and stack and consist to fill a cinema where several groups want to sit.
The statement of the problem is as it follows :
There is a cinema with maxRow * maxColumn seats (all rows having same number of seats).
n groups of people are arriving in order and trying to sit down. For each group, you get as input the numPersons number of peoples it consists of, and the row and seat their ticket is issued for. (Meaning they purchased the seats from ( row , seat ) to ( row , seat + numPersons - 1), these seats included.) The same seat might have been sold several times!
You have to simulate and output how many groups and how many persons could sit on their original appointed seat.
If a group cannot sit to its place because it is already occupied, they try to find another place using a seat finding method:
The group sits ONLY if ALL of them can sit next to each other.
They try to shift together: left by 1, right by 1, left by 2, right by 2, etc.
If unsuccessful in the row, then try 1 row towards the front, then by 1 row to back, 2 rows to front, etc.
In each row use the same seat-finding procedure (starting at the same seat position).
If they tried every possible places but still without success:
They adapt to the situation and split into two subgroups of same size (the first subgroup shall be bigger, if the size of the group was odd.).
Now the first subgroup tries to sit on the original row seat position using the same seat-finding procedure as above.
As the group size is now smaller, they might succeed this time.
If they don't succeed, they keep splitting the subgroupsize by another half, and continue trying, BEFORE the original another half subgroup gets the chance to try.
Note: Latest when the subgroup size reaches 1 person, it will always succeed to find a place.
After the whole original group got seated, the next group arrives, and so on.
You have to output two numbers:
groupSuccess is the number of groups, who could sit instantly in their original places. (If a split subgroup can sit later on the original place, that does not count towards this counter.)
personSuccess is the number of individuals, who could find a place on their original group ticket. A person shall be counted as successful, no matter after how many iterations (s)he found the place, if and only if the seat is any of the places appointed to the original group: ( row , seat ) to ( row , seat + numPersons - 1).
In first place, I created a "checkPlaces" function which checks if places at row parameter and comprised between column and column + numPersons - 1 are occupied :
public static boolean checkPlaces(int numPersons, int row, int column) {
for(int i = column; i < column + numPersons - 1; i++) {
if(cinema[row][i] == 1) {
return false;
}
}
return true;
}
I create another function "analyzeRow" where I check in calling "checkPlaces" function several times if original reserved places are available and if not I check consecutively
in shifting from 1, 2, ..., n column to the left and to right if the group can sit elsewhere on the row.
public static boolean analyzeRow(int numPersons, int row, int column, int maxRow, int maxColumn) {
if(checkPlaces(numPersons, row, column)) {
for(int i = column; i < column + numPersons - 1; i++) {
cinema[row][i] = 1;
}
return true;
}
else {
for(int j = 1; j < maxColumn; j++) {
if(j < column) {
int col1 = shiftLeftColumn(column, j);
if(checkPlaces(numPersons, row, col1)) {
for(int i = column; i < column + numPersons - 1; i++) {
cinema[row][i] = 1;
}
return true;
}
}
if(j < maxColumn - column) {
int col2 = shiftRightColumn(column, j);
if(checkPlaces(numPersons, row, col2)) {
for(int i = column; i < column + numPersons - 1; i++) {
cinema[row][i] = 1;
}
return true;
}
}
}
}
return false;
}
A third function "checkGrid" has for purpose to check the entire cinema in changing row
when analyzeRow sends false on a particular row in shifting from 1, 2, ..., n to front and to back until borders of the cinema are reached out.
public static boolean checkGrid(int numPersons, int row, int column, int maxRow, int maxColumn) {
if(!analyzeRow(numPersons, row, column, maxRow, maxColumn)) {
for(int l = 1; l < maxRow; l++) {
if(l < maxRow - row) {
int row1 = moveFrontRow(row, l);
analyzeRow(numPersons, row1, column, maxRow, maxColumn);
}
if(l < row) {
int row2 = moveBackRow(row, l);
analyzeRow(numPersons, row2, column, maxRow, maxColumn);
}
}
}
return true;
}
I also created several functions to shift column or row to left, right, front, back :
public static int shiftLeftColumn(int column, int j) {
int columnReturn = column - j;
return columnReturn;
}
public static int shiftRightColumn(int column, int j) {
int columnReturn = column + j;
return columnReturn;
}
public static int moveFrontRow(int row, int i) {
int rowReturn = row + i;
return rowReturn;
}
public static int moveBackRow(int row, int i) {
int rowReturn = row - i;
return rowReturn;
}
However, I have the feeling to have understood the idea, however I have difficulties to bring my code until the end and I'm not totally sure how I must do to take account the "split groups" part of the exercize, as the statement advice to use the "stack" concept, but I would rather use a binary tree that I create at the beginning of my main function with a static function :
TreeNode root = TreeNode.buildTree(numPersons);
Given that the exercize ask to split the group of people in two parts and then examine a first group and divide again to examine the two split groups issued of this first group,
before to examine the second.
It seems like leafs of a tree.
public class TreeNode {
int value;
TreeNode left;
TreeNode right;
public TreeNode(int value) {
this.value = value;
}
public TreeNode(int value, TreeNode left, TreeNode right) {
this.value = value;
this.left = left;
this.right = right;
}
public static TreeNode buildTree(int numPersons) {
while(numPersons > 1) {
if(numPersons % 2 == 0) {
TreeNode A = new TreeNode(numPersons, buildTree(numPersons/2), buildTree(numPersons/2));
return A;
}
else {
TreeNode B = new TreeNode(numPersons, buildTree((numPersons / 2) + 1), buildTree(numPersons / 2));
return B;
}
}
return new TreeNode(numPersons, null, null);
}
}
It made a long time I think on this, but I can't find how to finish and I would like to know if the way of thinking is the good or if I'm totally aside of the plate concerning the resolution of this exercize and I failed to see some algorithmic subtlety.
Thanks in advance to who could guide me.

Algorithm for seating firefighters in vehicle seats

I’m doing a project which involves placing volunteer firefighters, in seats when they arrive at the fire station to take the trucks.
Also some firefighters has drivers license some haven’t and some have a team leader education.
So my idea is to follow them by GPS and send the server a integer of distance to fire station and which education types each has.
Then every 5 sec, run the algorithm and based on the new GPS coordinates change their seats, until 1 is close to the station, and then mark is seat as taken.
This should happen until there is no empty seats or not anymore firefighters to call in or all called firefighters has arrived
The tricky thing I want help with (besides if my idea is the optimal), is to seat the firefighters most optimal.
I was thinking to make a prioritization list of possible roles.
And a prioritization list of vehicles which had to leave the station.
Then take the highest prioritized vehicle and the highest prioritized role, and fill it in with the closest firefighter which has the education.
But then if he is a driver but already set to the teamleaders seat, and only 1 more driver is coming, and there were more teamleaders coming, and two vehicles had to leave, it would be a wrong solution as the second vehicle couldn’t leave.
Again if Drivers and not teamleaders is the highest priority, then if the closest is set as driver, but is also the only one coming with a teamleader education, but more drivers are coming, then it would also be wrong.
Any ideas for the algorithm to work? Or does anybody knows an algorithm for this?
You're right, this type of greedy approach won't necessarily give you an optimal solution. Have you looked into/heard of Linear Programming or more specifically Integer programming: http://en.wikipedia.org/wiki/Integer_programming
In short integer programming is one technique for solving these types of scheduling problems. The problem has some objective you wish to maximise or minimise and is subject to various (linear) constraints. Integer because we can't have half a volunteer.
From your description the objective function could be to minimise the number of undeployed trucks and the total wait time. You could have different costs for different trucks to capture the different vehicle priorities.
Constraints would include each vehicle needing at least one driver, at least one team leader and that a volunteer can only be assigned to one vehicle. There might be other constraints you haven't described as well, such as no one can wait at the base for more than 20 minutes.
If you search for integer programming and scheduling you should find some code examples. You'll likely need a solver to assist. Wiki has a fairly comprehensive list, the choice will come down to your programming language preference and budget: http://en.wikipedia.org/wiki/Linear_programming#Solvers_and_scripting_.28programming.29_languages
How about something like this..
as you noticed the only conflicting situation is when you have a firefighter that can be driver and leader.. because the can only occupy one position.. but may be blocking another..
so don't start with them.. first start with the ones that have ether divers license OR Leaders education.. because those already have a predefined seat (the only they can take)..
after those are filled.. assign the ones that can do ether jobs, for the seats that are missing or replace some if they are nearer.
after having a queue of drivers and a queue of leaders.. sort them by distance to the firehouse and assign to trucks in pairs.. then fill the rest of the truck.. removing from a queue by order of ETA..
not sure if it will always give the best solution.. but seems quite optimal.. to to you wish some code? C#?
the question made me curious so. here is the code for what I was talking.. eve if you don't use it
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace FF
{
class Program
{
[Flags]
public enum Skill
{
None = 0,
Driver = 1,
TeamLeader = 2,
}
public class FireFighter : IComparable<FireFighter>
{
public int ID;
public Skill Skills;//the skills that he has
public Skill Assigned;//the one that he will be deployed with
public int ETA;
public override string ToString()
{
return ID + "(" + Skills + ")" + " # " + ETA + " minutes as " + this.Assigned;
}
int IComparable<FireFighter>.CompareTo(FireFighter other)
{
return this.ETA.CompareTo(other.ETA);
}
}
public class Truck
{
public int ID;
public int Capacity;
public List<FireFighter> Crew = new List<FireFighter>();
}
static int TotalStationTrucks = 8;
static List<Truck> Trucks = new List<Truck>();
static List<FireFighter> Team = new List<FireFighter>();
static Random Rnd = new Random();
static void Main(string[] args)
{
//create the sample data
int nAvailableSeats = 0;
for (int i = 0; i < TotalStationTrucks; i++)
{
Truck oTruck = new Truck();
oTruck.ID = i;
nAvailableSeats += oTruck.Capacity = Rnd.Next(4, 7);//seats between 4 - 6
Trucks.Add(oTruck);
}
for (int i = 0; i < nAvailableSeats * 2; i++)//add twice the amount of FF we need for all trucks
{
FireFighter oFireFighter = new FireFighter();
oFireFighter.ID = i;
oFireFighter.ETA = Rnd.Next(Int16.MaxValue);
Team.Add(oFireFighter);
}
for (int i = 0; i < Trucks.Count * 2; i++)//add twice the drivers we need
{
FireFighter oFireFighter = Team[Rnd.Next(Team.Count)];
oFireFighter.Skills |= Skill.Driver;
}
for (int i = 0; i < Trucks.Count * 2; i++)//add twice the leaders we need
{
FireFighter oFireFighter = Team[Rnd.Next(Team.Count)];
oFireFighter.Skills |= Skill.TeamLeader;
}
for (int i = 0; i < Trucks.Count * 2; i++)//add twice the multitaskers we need
{
FireFighter oFireFighter = Team[Rnd.Next(Team.Count)];
oFireFighter.Skills = (Skill.TeamLeader|Skill.Driver);
}
//Truck that are going to be deployed
int nTrucksToDeploy = Rnd.Next(2, Trucks.Count);
// distribute firefighter by ETA to minimize truck deploy
//*******************************************************
//Sort by ETA
Team.Sort();
//get top the ones that can only drive
List<FireFighter> oSelectedDrivers = Team.FindAll(delegate(FireFighter item) { return item.Skills == Skill.Driver; }).GetRange(0, nTrucksToDeploy);
oSelectedDrivers.ForEach(delegate(FireFighter item) { item.Assigned = Skill.Driver; });
//get top the ones that can only lead
List<FireFighter> oSelectedLeaders = Team.FindAll(delegate(FireFighter item) { return item.Skills == Skill.TeamLeader; }).GetRange(0, nTrucksToDeploy);
oSelectedLeaders.ForEach(delegate(FireFighter item) { item.Assigned = Skill.TeamLeader; });
//put them on a list of already assigned
List<FireFighter> oAssigned = new List<FireFighter>();
oAssigned.AddRange(oSelectedDrivers);
oAssigned.AddRange(oSelectedLeaders);
//get the ones that can do ether job
List<FireFighter> oMultitaskers = Team.FindAll(delegate(FireFighter item) { return item.Skills == (Skill.Driver | Skill.TeamLeader); });
//sort by ETA
oMultitaskers.Sort();
oAssigned.Sort();
//put a multitaskers in the queue if he is gonna arrive earlier than the most delayed
while (oMultitaskers[0].ETA < oAssigned[oAssigned.Count - 1].ETA)
{
FireFighter oIsOut = oAssigned[oAssigned.Count - 1];
FireFighter oIsIn = oMultitaskers[0];
//swap tasks
oIsIn.Assigned = oIsOut.Assigned;
oIsOut.Assigned = Skill.None;
//remove from respective queues
oAssigned.RemoveAt(oAssigned.Count - 1);
oMultitaskers.RemoveAt(0);
//Add the multitasker to queue
//and if you are questioning if the could get a better solution by choosing another assignment
//that as no influence.. the optmizing condition is removing the slowest that was on queue
oAssigned.Add(oIsIn);
//the out guy is not added for two reasons
//1st is not a multitasker
//2nd and most importante.. he was the one with the highest ETA, we will NEVER gain by replacing another driver with this one
//oMultitaskers.Add(oIsOut);
oMultitaskers.Sort();
oAssigned.Sort();
}
//start filling the trucks take one of each from top, wich means the first truck will have the earliest departure
for (int i = 0; i < nTrucksToDeploy; i++)
{
int nDriverIndex = oAssigned.FindIndex(delegate(FireFighter item) { return item.Assigned == Skill.Driver; });
Trucks[i].Crew.Add(oAssigned[nDriverIndex]);
oAssigned.RemoveAt(nDriverIndex);
int nLeaderIndex = oAssigned.FindIndex(delegate(FireFighter item) { return item.Assigned == Skill.TeamLeader; });
Trucks[i].Crew.Add(oAssigned[nLeaderIndex]);
oAssigned.RemoveAt(nLeaderIndex);
}
//now fill the rest of the crew.. also ordered by ETA
List<FireFighter> oUnassigned = Team.FindAll(delegate(FireFighter item) { return item.Assigned == Skill.None; });
oUnassigned.Sort();
for (int i = 0; i < nTrucksToDeploy; i++)
{
while (Trucks[i].Crew.Count < Trucks[i].Capacity)
{
Trucks[i].Crew.Add(oUnassigned[0]);
oUnassigned.RemoveAt(0);
}
}
//dump truck data
Trace.WriteLine(String.Format("{0} trucks to be deployed",nTrucksToDeploy));
for (int i = 0; i < nTrucksToDeploy; i++)
{
Trace.WriteLine(new String('-', 20));
Truck oTruck = Trucks[i];
oTruck.Crew.Sort();
Trace.WriteLine(String.Format("truck {0} estimated time of departure: {1}",oTruck.ID,oTruck.Crew[oTruck.Crew.Count-1].ETA));
for (int j = 0; j < oTruck.Crew.Count; j++)
{
FireFighter oFireFighter = oTruck.Crew[j];
Trace.WriteLine(String.Format("{0}({1})\t # {2} minutes as \t{3}", oFireFighter.ID, oFireFighter.Skills, oFireFighter.ETA, oFireFighter.Assigned));
}
}
Trace.WriteLine(new String('#', 20));
//list the team
for (int i = 0; i < Team.Count; i++)
{
FireFighter oFireFighter = Team[i];
Trace.WriteLine(String.Format("{0}({1})\t # {2} minutes as \t{3}", oFireFighter.ID, oFireFighter.Skills, oFireFighter.ETA, oFireFighter.Assigned));
}
}
}
}

implementing shuffle functioning in a music player

This was asked in an interview
"What is the most efficient way to implement a shuffle function in a music
player to play random songs without repetition"
I suggested link-list approach i.e. use a link-list, generate a random number and remove that item/song from the list ( this way , we ensure that no song is repeated )
then I suggested bit vector approach but he wasn't satisfied at all.
so what according to you is the best approach to implement such a function?
Below are some implementations. I also had difficulties during the interview but after the interview I saw that the solution is simple.
public class MusicTrackProgram {
// O(n) in-place swapping
public static List<MusicTrack> shuffle3(List<MusicTrack> input) {
Random random = new Random();
int last = input.size() - 1;
while (last >= 0) {
int randomInt = Math.abs(random.nextInt() % input.size());
// O(1)
MusicTrack randomTrack = input.get(randomInt);
MusicTrack temp = input.get(last);
// O(1)
input.set(last, randomTrack);
input.set(randomInt, temp);
--last;
}
return input;
}
// O(n) but extra field
public static List<MusicTrack> shuffle(List<MusicTrack> input) {
List<MusicTrack> result = new ArrayList<>();
Random random = new Random();
while (result.size() != input.size()) {
int randomInt = Math.abs(random.nextInt() % input.size());
// O(1)
MusicTrack randomTrack = input.get(randomInt);
if (randomTrack.isUsed) {
continue;
}
// O(1)
result.add(randomTrack);
randomTrack.isUsed = true;
}
return result;
}
// very inefficient O(n^2)
public static List<MusicTrack> shuffle2(List<MusicTrack> input) {
List<MusicTrack> result = new ArrayList<>();
Random random = new Random();
while (result.size() != input.size()) {
int randomInt = Math.abs(random.nextInt() % input.size());
// O(1)
MusicTrack randomTrack = input.get(randomInt);
// O(1)
result.add(randomTrack);
// O(n)
input.remove(randomTrack);
}
return result;
}
public static void main(String[] args) {
List<MusicTrack> musicTracks = MusicTrackFactory.generate(1000000);
List<MusicTrack> result = shuffle3(musicTracks);
result.stream().forEach(x -> System.out.println(x.getName()));
}
}
There is no perfect answer, I guess this sort of questions is aimed to start a discussion. Most likely your interviewer was wanting to hear about Fisher–Yates shuffle (aka Knuth shuffle).
Here is brief outline from wiki:
Write down the numbers from 1 through N.
Pick a random number k between one and the number of unstruck numbers remaining (inclusive).
Counting from the low end, strike out the kth number not yet struck out, and write it down elsewhere.
Repeat from step 2 until all the numbers have been struck out.
The sequence of numbers written down in step 3 is now a random permutation of the original numbers.
You should mention its inefficiencies and benefits, how you could improve this, throw in a few lines of code and discuss what and how you would test this code.
We can use link list and a queue for implementing a song search in mp3 player
We can extend this to following functionalities:
Add a new song
Delete a song
Randomly play a song
Add a song in play queue
Suppose initially we have 6 songs stored as link list
Link list has 2 pointers : start and end
totalSongCount=6
Randomly play a song:
We will generate a random number between 1 to totalSongCount. Let this be 4
We will remove the node representing song 4 and keep it after end pointer
we will decerement the totalSongCount (totalSongCount--).
Next time random number will be generated between 1 to 5 as we have decremented the totalSongCount , we can repeat the process
To add a new song, just add it to link list and make it as head pointer(add in beginning)
increment totalSongCount (totalSongCount++)
To delete a song , first find it and delete it
Also keep a track whether it is after end pointer , if it is not just decerement the totalSongCount (totalSongCount--)
The selected song can have two option:
Either play at that moment or
Add to a playlist (Seperate queue)
I think below solution should work
class InvalidInput extends Exception{
public InvalidInput(String str){
super(str);
}
}
class SongShuffler{
String songName[];
int cooldownPeriod;
Queue<String> queue;
int lastIndex ;
Random random;
public SongShuffler(String arr[], int k) throws InvalidInput{
if(arr.length < k)
throw new InvalidInput("Arr length should be greater than k");
songName = arr;
cooldownPeriod = k;
queue = new LinkedList<String>();
lastIndex = arr.length-1;
random = new Random();
}
public String getSong(){
if(queue.size() == cooldownPeriod){
String s = queue.poll();
songName[lastIndex+1] = s;
lastIndex++;
}
int ind = random.nextInt(lastIndex);
String ans = songName[ind];
queue.add(ans);
songName[ind] = songName[lastIndex];
lastIndex--;
return ans;
}
}

How can I divide 24 music albums into 6 playlists such that the running time / length is as evenly distributed as possible?

NOTE: I plan to implement this using Java, but any plain-English explanation of the steps needed in the logic is welcome and appreciated.
I'm trying to come up with a way to divide a group of 24 music albums/records into 6 playlists such that the lengths/running time all 6 playlists are as close to each other as possible.
I initially thought that maybe I could find all possible permutations of the problem and then work out a logic that will analyze which is the best division. I even created a thread to ask for help for it yesterday (I have 24 items that I need to separate into 6 sets of 4. What algorithm can I use to find all possible combinations?). However, when I got close to finding a solution, I realized that just finding all permutations of the problem will take incredibly long to run, so that approach seem impractical.
So I was wondering, is there a faster way to approach such a problem?
Given that these are the running times of the albums in question (in MM:SS format), what is the fastes way for me to find the division of albums into 6 playlists of 4 such that the lengths of each of the playlists is as close to each other as possible?
39:03
41:08
41:39
42:54
44:31
44:34
44:40
45:55
45:59
47:06
47:20
47:53
49:35
49:57
50:15
51:35
51:50
55:45
58:10
58:11
59:48
59:58
60:00
61:08
I did the math and considering the total time for all the albums, having 6 playlists that run at 200 minutes and 49 seconds would be perfect... but since the individual album lengths probably don't allow for that exact of a division, what would be the most exact possible division is my question.
NOTE: I could actually do this manually and get a close enough approximation that would suffice, but I am still really interested on how it could be done through a program.
Thanks!
I'd suggest you to use a Simulated annealing algorithm
And here is one nice solution derived by this algorithm:
[17, 16, 15, 9] 199:39
[3, 14, 10, 24] 199:50
[6, 8, 13, 21] 199:52
[1, 5, 20, 19] 199:55
[4, 23, 2, 18] 199:47
[11, 7, 22, 12] 199:51
As Steven Skiena pointed in his book ("The Algorithm Design Manual"), that it is very helpful to use Simulated annealing metaheuristic for finding acceptable solutions in real life combinatorial problems.
So, as you mentioned, you need to put 4 tracks in each of 6 albums such that all albums will have approximately the same duration.
Firstly lets consider - which property do we need to optimize?
From my point of view - the most suitable formulation would be: minimize standard deviation of durations of all albums. (But, if needed - you are free to include any other, more complex, properties).
Let's name the value of an optimized property as energy.
The main idea of algorithm
Each state of our system is characterized by some value of energy. By performing some actions over the system we can change its state (e.g. Swapping tracks between different albums).
Also, we have some abstract property called temperature.
When system is under high temperature, it is free to change its state to another state, even if the new state has a higher value of energy.
But when the temperature is small, the system tends to change its state mostly to new states with lower values of energy.
By physical analogy, the probability of changing the current state of system to a state with a higher value of energy can be limited in the same way as Boltzmann distribution defines.
Here is an illustration of how the standard deviation of durations changed while deriving the solution from above
Here is a full Java implementation of algorithm, which gives the solution from above
import java.util.Arrays;
import java.util.Random;
public class SimulatedAnnealingTracksOrdering {
public static void main(String[] args) {
int albumsCount = 6;
int tracksInAlbum = 4;
Album[] result = generateOptimalTracksOrdering(
tracksInAlbum,
albumsCount,
new Track[] {
new Track(1, "39:03"), new Track(2, "41:08"),
new Track(3, "41:39"), new Track(4, "42:54"),
new Track(5, "44:31"), new Track(6, "44:34"),
new Track(7, "44:40"), new Track(8, "45:55"),
new Track(9, "45:59"), new Track(10, "47:06"),
new Track(11, "47:20"), new Track(12, "47:53"),
new Track(13, "49:35"), new Track(14, "49:57"),
new Track(15, "50:15"), new Track(16, "51:35"),
new Track(17, "51:50"), new Track(18, "55:45"),
new Track(19, "58:10"), new Track(20, "58:11"),
new Track(21, "59:48"), new Track(22, "59:58"),
new Track(23, "60:00"), new Track(24, "61:08"),
});
for (Album album : result) {
System.out.println(album);
}
}
private static Album[] generateOptimalTracksOrdering(
int tracksInAlbum, int albumsCount, Track[] tracks) {
// Initialize current solution
Albums currentSolution =
new Albums(tracksInAlbum, albumsCount, tracks);
// Initialize energy of a current solution
double currentEnergy =
currentSolution.albumsDurationStandartDeviation();
System.out.println("Current energy is: " + currentEnergy);
// Also, we will memorize the solution with smallest value of energy
Albums bestSolution = currentSolution.clone();
double bestEnergy = currentEnergy;
// Constant, which defines the minimal value of energy
double minEnergy = 0.1;
// Initial temperature
double temperature = 150;
// We will decrease value of temperature, by multiplying on this
// coefficient
double alpha = 0.999;
// Constant, which defines minimal value of temperature
double minTemperature = 0.1;
// For each value of temperature - we will perform few probes, before
// decreasing temperature
int numberOfProbes = 100;
Random random = new Random(1);
while ((temperature > minTemperature)
&& (currentEnergy > minEnergy)) {
for (int i = 0; i < numberOfProbes; i++) {
// Generating new state
currentSolution.randomTracksPermutation();
double newEnergy =
currentSolution.albumsDurationStandartDeviation();
// As defined by Boltzmann distribution
double acceptanceProbability =
Math.exp(-(newEnergy - currentEnergy) / temperature);
// States with smaller energy - will be accepted always
if ((newEnergy < currentEnergy)
|| (random.nextDouble() < acceptanceProbability)) {
currentEnergy = newEnergy;
System.out.println("Current energy is: " + currentEnergy);
if (newEnergy < bestEnergy) {
bestSolution = currentSolution.clone();
bestEnergy = newEnergy;
}
} else {
// If state can't be accepted - rollback to previous state
currentSolution.undoLastPermutation();
}
}
// Decreasing temperature
temperature *= alpha;
}
// Return best solution
return bestSolution.getAlbums();
}
}
/**
* Container for bunch of albums
*/
class Albums {
private Random random = new Random(1);
private Album[] albums;
// These fields, are used for memorizing last permutation
// (needed for rollbacking)
private Album sourceAlbum;
private int sourceIndex;
private Album targetAlbum;
private int targetIndex;
public Albums(int tracksInAlbum, int albumsCount, Track[] tracks) {
// Put all tracks to albums
this.albums = new Album[albumsCount];
int t = 0;
for (int i = 0; i < albumsCount; i++) {
this.albums[i] = new Album(tracksInAlbum);
for (int j = 0; j < tracksInAlbum; j++) {
this.albums[i].set(j, tracks[t]);
t++;
}
}
}
/**
* Calculating standard deviations of albums durations
*/
public double albumsDurationStandartDeviation() {
double sumDuration = 0;
for (Album album : this.albums) {
sumDuration += album.getDuraion();
}
double meanDuration =
sumDuration / this.albums.length;
double sumSquareDeviation = 0;
for (Album album : this.albums) {
sumSquareDeviation +=
Math.pow(album.getDuraion() - meanDuration, 2);
}
return Math.sqrt(sumSquareDeviation / this.albums.length);
}
/**
* Performing swapping of random tracks between random albums
*/
public void randomTracksPermutation() {
this.sourceAlbum = this.pickRandomAlbum();
this.sourceIndex =
this.random.nextInt(this.sourceAlbum.getTracksCount());
this.targetAlbum = this.pickRandomAlbum();
this.targetIndex =
this.random.nextInt(this.targetAlbum.getTracksCount());
this.swapTracks();
}
public void undoLastPermutation() {
this.swapTracks();
}
private void swapTracks() {
Track sourceTrack = this.sourceAlbum.get(this.sourceIndex);
Track targetTrack = this.targetAlbum.get(this.targetIndex);
this.sourceAlbum.set(this.sourceIndex, targetTrack);
this.targetAlbum.set(this.targetIndex, sourceTrack);
}
private Album pickRandomAlbum() {
int index = this.random.nextInt(this.albums.length);
return this.albums[index];
}
public Album[] getAlbums() {
return this.albums;
}
private Albums() {
// Needed for clonning
}
#Override
protected Albums clone() {
Albums cloned = new Albums();
cloned.albums = new Album[this.albums.length];
for (int i = 0; i < this.albums.length; i++) {
cloned.albums[i] = this.albums[i].clone();
}
return cloned;
}
}
/**
* Container for tracks
*/
class Album {
private Track[] tracks;
public Album(int size) {
this.tracks = new Track[size];
}
/**
* Duration of album == sum of durations of tracks
*/
public int getDuraion() {
int acc = 0;
for (Track track : this.tracks) {
acc += track.getDuration();
}
return acc;
}
public Track get(int trackNum) {
return this.tracks[trackNum];
}
public void set(int trackNum, Track track) {
this.tracks[trackNum] = track;
}
public int getTracksCount() {
return this.tracks.length;
}
public Track[] getTracks() {
return this.tracks;
}
#Override
protected Album clone() {
Album cloned = new Album(this.tracks.length);
for (int i = 0; i < this.tracks.length; i++) {
cloned.tracks[i] = this.tracks[i];
}
return cloned;
}
/**
* Displaying duration in MM:SS format
*/
#Override
public String toString() {
int duraion = this.getDuraion();
String duration_MM_SS = (duraion / 60) + ":" + (duraion % 60);
return Arrays.toString(this.tracks) + "\t" + duration_MM_SS;
}
}
class Track {
private final int id;
private final int durationInSeconds;
public Track(int id, String duration_MM_SS) {
this.id = id;
this.durationInSeconds =
this.parseDuration(duration_MM_SS);
}
/**
* Converting MM:SS duration to seconds
*/
private int parseDuration(String duration_MM_SS) {
String[] parts = duration_MM_SS.split(":");
return (Integer.parseInt(parts[0]) * 60)
+ Integer.parseInt(parts[1]);
}
public int getDuration() {
return this.durationInSeconds;
}
public int getId() {
return this.id;
}
#Override
public String toString() {
return Integer.toString(this.id);
}
}
This is equivalent* to multiprocessor scheduling if you consider each album as a job and each playlist as a processor, and finding an optimal solution is NP-hard.
However, there are efficient algorithms which give decent but not necessarily optimal results. For example, sorting the albums by length and repeatedly adding the longest album to the shortest playlist.
If we number the albums from 1 to 24 from shortest to longest, this algorithm gives the following division.
{24, 13, 9, 6} (201:16)
{23, 14, 12, 2} (198:58)
{22, 15, 10, 4} (200:13)
{21, 16, 8, 5} (201:49)
{20, 17, 11, 3} (199:00)
{19, 18, 7, 1} (197:38)
* If we consider "evenly distributed" to mean that the length of the longest playlist is minimized.
With a more intelligent search algorithm than brute force, we don't have to go through all 1e12 possibilities. First we convert the input, enumerate all sets of four, and sort them by their proximity to the target time.
import heapq
import itertools
import re
def secondsfromstring(s):
minutes, seconds = s.split(':')
return int(minutes) * 60 + int(seconds)
def stringfromseconds(seconds):
minutes, seconds = divmod(seconds, 60)
return '{}:{:02}'.format(minutes, seconds)
# for simplicity, these have to be pairwise distinct
stringtimes = '''39:03 41:08 41:39 42:54 44:31 44:34
44:40 45:55 45:59 47:06 47:20 47:53
49:35 49:57 50:15 51:35 51:50 55:45
58:10 58:11 59:48 59:58 60:00 61:08'''
times = [secondsfromstring(s) for s in stringtimes.split()]
quads = [frozenset(quad) for quad in itertools.combinations(times, 4)]
targettime = sum(times) / 6
quads.sort(key=lambda quad: abs(sum(quad) - targettime))
Now comes a search. We keep a priority queue with partial solutions, ordered by the minimum possible maximum deviation from the target time. The priority queue lets us explore the most promising partial solutions first.
queue = [(0, frozenset(times), [])]
while True:
i, remaining, sofar = heapq.heappop(queue)
if not remaining:
for quad in sofar:
print(stringfromseconds(sum(quad)), ':',
*(stringfromseconds(time) for time in quad))
break
while i < len(quads):
quad = quads[i]
if quad.issubset(remaining):
heapq.heappush(queue, (i + 1, remaining, sofar))
heapq.heappush(queue, (i + 1, remaining - quad, sofar + [quad]))
break
i += 1
In a couple seconds, this code spits out the following optimal answer. (We got lucky, since this code is working on the slightly modified objective of minimizing the maximum deviation from the target time; with the more complicated program below, we can minimize the difference between minimum and maximum, which turns out to be the same grouping.)
199:50 : 47:06 41:39 61:08 49:57
199:52 : 44:34 45:55 59:48 49:35
199:45 : 55:45 41:08 59:58 42:54
199:53 : 44:40 47:20 60:00 47:53
199:55 : 58:10 44:31 58:11 39:03
199:39 : 51:35 50:15 51:50 45:59
The program that optimizes the max minus min objective is below. Compared to the above program, it doesn't stop after the first solution but instead waits until we start considering sets whose deviation from the target is greater than the smallest max minus min of solutions that we have found so far. Then it outputs the best.
import heapq
import itertools
import re
def secondsfromstring(s):
minutes, seconds = s.split(':')
return int(minutes) * 60 + int(seconds)
def stringfromseconds(seconds):
minutes, seconds = divmod(seconds, 60)
return '{}:{:02}'.format(minutes, seconds)
# for simplicity, these have to be pairwise distinct
stringtimes = '''39:03 41:08 41:39 42:54 44:31 44:34
44:40 45:55 45:59 47:06 47:20 47:53
49:35 49:57 50:15 51:35 51:50 55:45
58:10 58:11 59:48 59:58 60:00 61:08'''
times = [secondsfromstring(s) for s in stringtimes.split()]
quads = [frozenset(quad) for quad in itertools.combinations(times, 4)]
targettime = sum(times) / 6
quads.sort(key=lambda quad: abs(sum(quad) - targettime))
def span(solution):
quadtimes = [sum(quad) for quad in solution]
return max(quadtimes) - min(quadtimes)
candidates = []
bound = None
queue = [(0, frozenset(times), [])]
while True:
i, remaining, sofar = heapq.heappop(queue)
if not remaining:
candidates.append(sofar)
newbound = span(sofar)
if bound is None or newbound < bound: bound = newbound
if bound is not None and abs(sum(quads[i]) - targettime) >= bound: break
while i < len(quads):
quad = quads[i]
i += 1
if quad.issubset(remaining):
heapq.heappush(queue, (i, remaining, sofar))
heapq.heappush(queue, (i, remaining - quad, sofar + [quad]))
break
best = min(candidates, key=span)
for quad in best:
print(stringfromseconds(sum(quad)), ':',
*(stringfromseconds(time) for time in quad))
This can be a comment too, but it is too long, so I post it as an answer. This is a little easy-to-code improvement to hammar's solution. This algorithm gives You no optimal solution, but it finds a better one.
You can start with hammar's greedy algorithm to fill the playlists with albums. Next all You have to do is looping through them few times.
Let D the difference between the total length of playlist A and playlist B, where length(A)>length(B). Loop through playlists A and B and if you find albums x \in A and y \in B which statisfies x>y && x-y<D, swap x and y.
This gave me the following results:
{7,8,22,13} 200:8
{2,11,24,15} 199:51
{4,10,23,14} 199:57
{3,17,12,19} 199:32
{5,16,21,6} 200:28
{1,18,9,20} 198:58
For what it's worth, before stemm posted his Simulated annealing algorithm, I already had a solution which came to a close enough approximation of what I was looking for. So, just to share another (albeit relatively crude) approach, here it is:
(1) I sorted all of the individual track times from shortest to longest.
#01 - 39:03
#02 - 41:08
#03 - 41:39
#04 - 42:54
#05 - 44:31
#06 - 44:34
#07 - 44:40
#08 - 45:55
#09 - 45:59
#10 - 47:06
#11 - 47:20
#12 - 47:53
#13 - 49:35
#14 - 49:57
#15 - 50:15
#16 - 51:35
#17 - 51:50
#18 - 55:45
#19 - 58:10
#20 - 58:11
#21 - 59:48
#22 - 59:58
#23 - 60:00
#24 - 61:08
(2) I grouped into groups of 4 such that the resulting playlists would theoretically have as short a distance to each other just based on their positions on the sorted list, and that grouping would be: the shortest item + the longest item + the middle two items... then from the remaining (still sorted) list, again the shortest + the longest + the middle two items... so on and so forth until all items are grouped together.
(3) Using the resulting sets of 4 now, I iterate through all the individual albums in each of the sets, swapping out each item for every other item from the other sets. If, when swapped out, the distance of their source sets become smaller, then commit the swap. Else, skip the swap. Then I just did that repeatedly until no two albums can be swapped anymore to lessen the distance between their parent sets.
To illustrate step #3 in code:
Partitioner(){
for(int i=0;i<total.length;i++)
total[i] = 0;
// get the total time for all sets of four albums
for(int i=0;i<sets.length;i++){
for(int j=0;j<sets[i].length;j++){
total[i] = total[i] + sets[i][j];
}
}
// this method will be called recursively until no more swaps can be done
swapper();
for(int i=0;i<sets.length;i++){
for(int j=0;j<sets[i].length;j++){
System.out.println(sets[i][j]);
}
}
}
void swapper(){
int count = 0;
for(int i=0;i<sets.length;i++){ // loop through all the sets
for(int j=0;j<sets[i].length;j++){ // loop through all the album in a set
for(int k=0;k<sets.length;k++){ // for every album, loop through all the other sets for comparison
if(k!=i){ // but of course, don't compare it to its own set
for(int l=0;l<sets[k].length;l++){ // pair the album (sets[i][j]) with every album from the other sets
int parentA = total[i]; // store the total length from the parent set of album sets[i][j]
int parentB = total[k]; // store the total length from the parent set of the album being compared
int origDist = Math.abs(parentA - parentB); // measure the original distance between those two sets
int newA = total[i] - sets[i][j] + sets[k][l]; //compute new length of "parentA" if the two albums were swapped
int newB = total[k] - sets[k][l] + sets[i][j]; //compute new length of "parentB" if the two albums were swapped
int newdist = Math.abs(newA - newB); //compute new distance if the two albums were swapped
if(newdist<origDist){ // if the new resulting distance is less than the original distance, commit the swap
int temp = sets[i][j];
sets[i][j] = sets[k][l];
sets[k][l] = temp;
total[i] = newA;
total[k] = newB;
count++;
}
}
}
}
}
}
System.out.println(count);
if (count > 0 ){
swapper();
}
}

What class of algorithms reduce margin of error in continuous stream of input?

A machine is taking measurements and giving me discrete numbers continuously like so:
1 2 5 7 8 10 11 12 13 14 18
Let us say these measurements can be off by 2 points and a measurement is generated every 5 seconds. I want to ignore the measurements that may potentially be same
Like continuous 2 and 3 could be same because margin of error is 2 so how do I partition the data such that I get only distinct measurements but I would also want to handle the situation in which the measurements are continuously increasing like so:
1 2 3 4 5 6 7 8 9 10
In this case if we keep ignoring the consecutive numbers with difference of less than 2 then we might lose actual measurements.
Is there a class of algorithms for this? How would you solve this?
Just drop any number that comes 'in range of' the previous (kept) one. It should simply work.
For your increasing example:
1 is kept, 2 is dropped because it is in range of 1, 3 is dropped because it is in range of 1, then 4 is kept, 5 and 6 are dropped in range of 4, then 7 is kept, etc, so you still keep the increasing trend if it's big enough (which is what you want, right?
For the original example, you'd get 1,5,8,11,14,18 as a result.
In some lines of work, the standard way to deal with problems of this nature is by using the Kalman filter.
To quote Wikipedia:
Its [Kalman filter's] purpose is to use measurements
observed over time, containing noise
(random variations) and other
inaccuracies, and produce values that
tend to be closer to the true values
of the measurements and their
associated calculated values.
The filter itself is very easy to implement, but does require calibration.
I would have two queues:
Temporary Queue
Final Queue/List
Your first value would go into the temporary queue and in the final list. As new values come in, check to see if the new value is within the deadband of the last value in the list. If it is then add it to the temporary queue. If not then add it to the final list. If your temporary queue starts to increase in size before you get a new value outside of the deadband, then once you are outside of the deadband do a check to see if the values are monotonically increasing or decreasing the whole time. If they are always increasing or decreasing then add the contents of the queue to the final list, otherwise just add the single new value to the final list. This is the general gist of it.
Here is some code I whipped up quickly that implements a class to do what I described above:
public class MeasurementsFilter
{
private Queue<int> tempQueue = new Queue<int>();
private List<int> finalList = new List<int>();
private int deadband;
public MeasurementsFilter(int deadband)
{
this.deadband = deadband;
}
public void Reset()
{
finalList.Clear();
tempQueue.Clear();
}
public int[] FinalValues()
{
return finalList.ToArray();
}
public void AddNewValue(int value)
{
// if we are just starting then the first value always goes in the list and queue
if (tempQueue.Count == 0)
{
tempQueue.Enqueue(value);
finalList.Add(value);
}
else
{
// if the new value is within the deadband of the last value added to the final list
// then enqueue the value and wait
if ((tempQueue.Peek() - deadband <= value) && (value <= tempQueue.Peek() + deadband))
{
tempQueue.Enqueue(value);
}
// else the new value is outside of the deadband of the last value added to the final list
else
{
tempQueue.Enqueue(value);
if (QueueIsAlwaysIncreasingOrAlwaysDecreasing())
{
//dequeue first item (we already added it to the list before, but we need it for comparison purposes)
int currentItem = tempQueue.Dequeue();
while (tempQueue.Count > 0)
{
// if we are not seeing two in a row of the same (i.e. they are not duplicates of each other)
// then add the newest value to the final list
if (currentItem != tempQueue.Peek())
{
currentItem = tempQueue.Dequeue();
finalList.Add(currentItem);
}
// otherwise if we are seeing two in a row (i.e. duplicates)
// then discard the value and loop to the next value
else
{
currentItem = tempQueue.Dequeue();
}
}
// add the last item from the final list back into the queue for future deadband comparisons
tempQueue.Enqueue(finalList[finalList.Count - 1]);
}
else
{
// clear the queue and add the new value to the list and as the starting point of the queue
// for future deadband comparisons
tempQueue.Clear();
tempQueue.Enqueue(value);
finalList.Add(value);
}
}
}
}
private bool QueueIsAlwaysIncreasingOrAlwaysDecreasing()
{
List<int> queueList = new List<int>(tempQueue);
bool alwaysIncreasing = true;
bool alwaysDecreasing = true;
int tempIncreasing = int.MinValue;
int tempDecreasing = int.MaxValue;
int i = 0;
while ((alwaysIncreasing || alwaysDecreasing) && (i < queueList.Count))
{
if (queueList[i] >= tempIncreasing)
tempIncreasing = queueList[i];
else
alwaysIncreasing = false;
if (queueList[i] <= tempDecreasing)
tempDecreasing = queueList[i];
else
alwaysDecreasing = false;
i++;
}
return (alwaysIncreasing || alwaysDecreasing);
}
}
Here is some test code that you can throw into a Winform Load event or button click:
int[] values = new int[] { 1, 2, 2, 1, 4, 8, 3, 2, 1, 0, 6 };
MeasurementsFilter filter = new MeasurementsFilter(2);
for (int i = 0; i < values.Length; i++)
{
filter.AddNewValue(values[i]);
}
int[] finalValues = filter.FinalValues();
StringBuilder printValues = new StringBuilder();
for (int i = 0; i < finalValues.Length; i++)
{
printValues.Append(finalValues[i]);
printValues.Append(" ");
}
MessageBox.Show("The final values are: " + printValues);

Resources