Say you have a shipment. It needs to go from point A to point B, point B to point C and finally point C to point D. You need it to get there in five days for the least amount of money possible. There are three possible shippers for each leg, each with their own different time and cost for each leg:
Array
(
[leg0] => Array
(
[UPS] => Array
(
[days] => 1
[cost] => 5000
)
[FedEx] => Array
(
[days] => 2
[cost] => 3000
)
[Conway] => Array
(
[days] => 5
[cost] => 1000
)
)
[leg1] => Array
(
[UPS] => Array
(
[days] => 1
[cost] => 3000
)
[FedEx] => Array
(
[days] => 2
[cost] => 3000
)
[Conway] => Array
(
[days] => 3
[cost] => 1000
)
)
[leg2] => Array
(
[UPS] => Array
(
[days] => 1
[cost] => 4000
)
[FedEx] => Array
(
[days] => 1
[cost] => 3000
)
[Conway] => Array
(
[days] => 2
[cost] => 5000
)
)
)
How would you go about finding the best combination programmatically?
My best attempt so far (third or fourth algorithm) is:
Find the longest shipper for each leg
Eliminate the most "expensive" one
Find the cheapest shipper for each leg
Calculate the total cost & days
If days are acceptable, finish, else, goto 1
Quickly mocked-up in PHP (note that the test array below works swimmingly, but if you try it with the test array from above, it does not find the correct combination):
$shippers["leg1"] = array(
"UPS" => array("days" => 1, "cost" => 4000),
"Conway" => array("days" => 3, "cost" => 3200),
"FedEx" => array("days" => 8, "cost" => 1000)
);
$shippers["leg2"] = array(
"UPS" => array("days" => 1, "cost" => 3500),
"Conway" => array("days" => 2, "cost" => 2800),
"FedEx" => array("days" => 4, "cost" => 900)
);
$shippers["leg3"] = array(
"UPS" => array("days" => 1, "cost" => 3500),
"Conway" => array("days" => 2, "cost" => 2800),
"FedEx" => array("days" => 4, "cost" => 900)
);
$times = 0;
$totalDays = 9999999;
print "<h1>Shippers to Choose From:</h1><pre>";
print_r($shippers);
print "</pre><br />";
while($totalDays > $maxDays && $times < 500){
$totalDays = 0;
$times++;
$worstShipper = null;
$longestShippers = null;
$cheapestShippers = null;
foreach($shippers as $legName => $leg){
//find longest shipment for each leg (in terms of days)
unset($longestShippers[$legName]);
$longestDays = null;
if(count($leg) > 1){
foreach($leg as $shipperName => $shipper){
if(empty($longestDays) || $shipper["days"] > $longestDays){
$longestShippers[$legName]["days"] = $shipper["days"];
$longestShippers[$legName]["cost"] = $shipper["cost"];
$longestShippers[$legName]["name"] = $shipperName;
$longestDays = $shipper["days"];
}
}
}
}
foreach($longestShippers as $leg => $shipper){
$shipper["totalCost"] = $shipper["days"] * $shipper["cost"];
//print $shipper["totalCost"] . " <?> " . $worstShipper["totalCost"] . ";";
if(empty($worstShipper) || $shipper["totalCost"] > $worstShipper["totalCost"]){
$worstShipper = $shipper;
$worstShipperLeg = $leg;
}
}
//print "worst shipper is: shippers[$worstShipperLeg][{$worstShipper['name']}]" . $shippers[$worstShipperLeg][$worstShipper["name"]]["days"];
unset($shippers[$worstShipperLeg][$worstShipper["name"]]);
print "<h1>Next:</h1><pre>";
print_r($shippers);
print "</pre><br />";
foreach($shippers as $legName => $leg){
//find cheapest shipment for each leg (in terms of cost)
unset($cheapestShippers[$legName]);
$lowestCost = null;
foreach($leg as $shipperName => $shipper){
if(empty($lowestCost) || $shipper["cost"] < $lowestCost){
$cheapestShippers[$legName]["days"] = $shipper["days"];
$cheapestShippers[$legName]["cost"] = $shipper["cost"];
$cheapestShippers[$legName]["name"] = $shipperName;
$lowestCost = $shipper["cost"];
}
}
//recalculate days and see if we are under max days...
$totalDays += $cheapestShippers[$legName]['days'];
}
//print "<h2>totalDays: $totalDays</h2>";
}
print "<h1>Chosen Shippers:</h1><pre>";
print_r($cheapestShippers);
print "</pre>";
I think I may have to actually do some sort of thing where I literally make each combination one by one (with a series of loops) and add up the total "score" of each, and find the best one....
EDIT:
To clarify, this isn't a "homework" assignment (I'm not in school). It is part of my current project at work.
The requirements (as always) have been constantly changing. If I were given the current constraints at the time I began working on this problem, I would be using some variant of the A* algorithm (or Dijkstra's or shortest path or simplex or something). But everything has been morphing and changing, and that brings me to where I'm at right now.
So I guess that means I need to forget about all the crap I've done to this point and just go with what I know I should go with, which is a path finding algorithm.
Could alter some of the shortest path algorithms, like Dijkstra's, to weight each path by cost but also keep track of time and stop going along a certain path if the time exceeds your threshold. Should find the cheapest that gets you in under your threshold that way
Sounds like what you have is called a "linear programming problem". It also sounds like a homework problem, no offense.
The classical solution to a LP problem is called the "Simplex Method". Google it.
However, to use that method, you must have the problem correctly formulated to describe your requirements.
Still, it may be possible to enumerate all possible paths, since you have such a small set. Such a thing won't scale, though.
Sounds like a job for Dijkstra's algorithm:
Dijkstra's algorithm, conceived by Dutch computer scientist Edsger Dijkstra in 1959, 1 is a graph search algorithm that solves the single-source shortest path problem for a graph with non negative edge path costs, outputting a shortest path tree. This algorithm is often used in routing.
There are also implementation details in the Wikipedia article.
If I knew I only had to deal with 5 cities, in a predetermined order, and that there were only 3 routes between adjacent cities, I'd brute force it. No point in being elegant.
If, on the other hand, this were a homework assignment and I were supposed to produce an algorithm that could actually scale, I'd probably take a different approach.
As Baltimark said, this is basically a Linear programming problem. If only the coefficients for the shippers (1 for included, 0 for not included) were not (binary) integers for each leg, this would be more easily solveable. Now you need to find some (binary) integer linear programming (ILP) heuristics as the problem is NP-hard.
See Wikipedia on integer linear programming for links; on my linear programming course we used at least Branch and bound.
Actually now that I think of it, this special case is solveable without actual ILP as the amount of days does not matter as long as it is <= 5. Now start by choosing the cheapest carrier for first choice (Conway 5:1000). Next you choose yet again the cheapest, resulting 8 days and 4000 currency units which is too much so we abort that. By trying others too we see that they all results days > 5 so we back to first choice and try the second cheapest (FedEx 2:3000) and then ups in the second and fedex in the last. This gives us total of 4 days and 9000 currency units.
We then could use this cost to prune other searches in the tree that would by some subtree-stage result costs larger that the one we've found already and leave that subtree unsearched from that point on.
This only works as long as we can know that searching in the subtree will not produce a better results, as we do here when costs cannot be negative.
Hope this rambling helped a bit :).
This is a knapsack problem. The weights are the days in transit, and the profit should be $5000 - cost of leg. Eliminate all negative costs and go from there!
I think that Dijkstra's algorithm is for finding a shortest path.
cmcculloh is looking for the minimal cost subject to the constraint that he gets it there in 5 days.
So, merely finding the quickest way won't get him there cheapest, and getting there for the cheapest, won't get it there in the required amount of time.
Related
Let's explain my problem!
we are coding a website for player of some games we know that the minimum of player is 2 and don't know the maximum of player.
in these games we have a leaderboard (1st,second,...,Last) and we want to distribute for exemple 50 points in an exponential or other way to make graphs.
so here is my question how to give these 50 points to the players with the rules that the first must have the most of points and the last must not have points.
I'm open for suggestions,
and thank all of you who can help me
You can simply remap a priority.
You were asking the last not having any points, try to add one additional player and remove after calculation.
Let's say we want to distribute 50 points over 10 players by descending priority.
$pointsAvailable = 50;
$playerCount = 10;
We set some kind of priority.
$players = range(1, $playerCount);
$players = array_map(function($p) use ($playerCount, $pointsAvailable) {
return ($playerCount / $p);
}, $players);
Now we know we divide the sum of the distribution by the number of points to get the scalar factor.
$pointScalar = $pointsAvailable / array_sum($players);
$players = array_map(function($p) use ($pointScalar) {
return ($pointScalar * $p);
}, $players);
Here you can see the results.
print_r($players);
Array // The points for the players
(
[0] => 17.07085760737
[1] => 8.5354288036851
[2] => 5.6902858691234
[3] => 4.2677144018426
[4] => 3.4141715214741
[5] => 2.8451429345617
[6] => 2.43869394391
[7] => 2.1338572009213
[8] => 1.8967619563745
[9] => 1.707085760737
)
The sum of all points:
print_r(array_sum($players));
50
I have an array that contains half-hourly time periods; this array is then sub-divided into dates going back 6 weeks of 'today', each date having the amount of transactions completed on that date, in that half hour period. e.g.
[07:30:00] => Array
(
[2015-05-18] => 10
[2015-05-25] => 17
[2015-06-01] => 11
[2015-06-08] => 20
[2015-06-15] => 16
[average] => 15
)
[08:00:00] => Array
(
[2015-05-18] => 12
[2015-05-25] => 10
[2015-06-01] => 14
[2015-06-08] => 19
[2015-06-15] => 18
[average] => 15
)
This goes on for the entire business day.
The average I'm currently calculating above is a simple mean of all those values, calculated thus:
foreach($average as $half_hour => $data) {
$average[$half_hour]['average'] = round(array_sum($data)/count($data), 0, PHP_ROUND_HALF_UP);
}
What I'd like to achieve is applying more weight to the more recent dates, and lesser weight to the older values. I started off defining an array of dates I'd encounter, and setting a weight for that particular date:
$date_weights = array(
date("Y-m-d", strtotime("6 weeks ago $day")) => 0.05,
date("Y-m-d", strtotime("5 weeks ago $day")) => 0.05,
date("Y-m-d", strtotime("4 weeks ago $day")) => 0.15,
date("Y-m-d", strtotime("3 weeks ago $day")) => 0.20,
date("Y-m-d", strtotime("2 weeks ago $day")) => 0.25,
date("Y-m-d", strtotime("1 weeks ago $day")) => 0.30
);
The weights all add up to 1, which from what I understand of weighted mean calculations, keeps things simple(r). Except, this is where my poor-math brain threw an exception and refused to play any more.
I tried the following code, but that appears not to do what I'm looking for:
foreach($average as $half_hour => $data) {
// apply the desired weighting to the values
foreach($data as $date => $transactions) {
$average[$half_hour][$date] = ($date_weights[$date]*$transactions);
}
}
foreach($average as $half_hour => $data) {
$average[$half_hour]['average'] = round(array_sum($data)/count($data), 0, PHP_ROUND_HALF_UP);
}
I think I'm on the right track here ? But I'm obviously missing a step. Can any kind slightly more math-savvy person point out where I'm going wrong and how best to achieve where I'm trying to get with this? Thank you !!
Simply remove the division by count($data) so that you get
$average[$half_hour]['average'] = round(array_sum($data), 0, PHP_ROUND_HALF_UP);
Or pick weights that have an average of (instead of a sum) of one, and keep the division.
Also, if you have 6 weight you need to have 6 values - otherwise the weights don't add up to one. Now you seem to have 6 weights (in $date_weights but only 5 values (in $average[$half_hour], not counting average).
EDIT: If you don't want to worry about the weights adding up to one, you can use this:
$average[$half_hour]['average'] = round(array_sum($data)/array_sum($date_weights), 0, PHP_ROUND_HALF_UP);
You still have to make sure all of the weights are used, though.
I am attempting to count the number of occurrences of every unique word on a page (think SEO 'word count' that you see on woorank etc. - but not for that purpose!)
I am really struggling on how to set this up:-
At the moment I am thinking of reading each word and then checking if it is unique against an array -> if unique add to array with occurences=>1 - then if I find the same word later just +1.
However this seems really cumbersome and slow for large blocks of text (especially as I will have to strip commas etc, convert all to lower case etc.) -> is there are a better way, has someone got a code snippet or library for this task?
For clarity
The Cat ran away with the hat. The spoon had already run away with another cat, far far away.
Would yield:
the => 3,
away => 3,
cat => 2,
with => 2,
far => 2,
spoon => 1,
hat => 1,
ran => 1,
run => 1,
had => 1,
another => 1,
already => 1
Thanks in advance - if there is no better way then that is fine!
ASIDE
I contemplated do a replace($word,"") on all words once found and counted -> but this seems just as cumbersome.
Use array_count_values() in conjunction with str_word_count():
$wordCounts = array_count_values(str_word_count(strtolower($sentence), 1));
arsort($wordCounts);
Output:
Array
(
[the] => 3
[away] => 3
[cat] => 2
[far] => 2
[with] => 2
[run] => 1
[another] => 1
[already] => 1
[hat] => 1
[ran] => 1
[spoon] => 1
[had] => 1
)
Demo
have a look at this article: http://net.tutsplus.com/tutorials/javascript-ajax/spotlight-jquery-replacetext/
Split all the words (you could use a tokenizer like the ones users in Solr to "clean" them), put then in array, sort it, and array unique count. It really would depend on the language, but it will always be faster to use the language native functions that iterate the text by yourself.
In php:
$array = preg_split('/[\s,\.]+/', strtolower($text));
$unique = array_count_values($array);
print_r($unique);
here is a little background on what I'm trying to accomplish. I have an array from a MySQL query that is being displayed. I want to sort the array based on a factor. The factor is calculated inline based on the time the article was posted & the number of votes it's received. Something like this:
// ... MySQL query here
$votes = $row['0']
$seconds = strtotime($record->news_time)+time();
$sum_total = pow($votes,2) / $seconds;
So the array thats coming in looks something like this:
Array (
[0] => stdClass Object (
[id] => 13
[news_title] => Article
[news_url] => http://website.com/article/14
[news_root_domain] => website.com
[news_category] => Business
[news_submitter] => 2
[news_time] => 2013-02-18 12:50:02
[news_points] => 2
)
[1] => stdClass Object (
[id] => 14
[news_title] => Title
[news_url] => http://www.website.com/article/2
[news_root_domain] => www.website.com
[news_category] => Technology
[news_submitter] => 1
[news_time] => 2012-10-02 10:03:22
[news_points] => 8
)
)
I want to sort the aforementioned array using the factor I mentioned above. The idea is to show the highest rated articles first on the list (using the calculated factor), instead of the default sorting method that the array comes in. It seems like usort might be my best bet, but let me know what you all think?
Do it all in the query:
SELECT n.*, ( POW(?, 2) / (UNIX_TIMESTAMP(n.news_time) + UNIX_TIMESTAMP(NOW())) ) as rank
FROM news_table n
ORDER BY rank;
Now in order to get the votes you may have to do a subquery or a join, but i cant advise on that because you dont give enough info on where the votes are coming from. You could however supply the votes to the query as well instead of selecting it all in one shot something like:
$sql = sprintf('SELECT n.*, ( POW(%d, 2) / (UNIX_TIMESTAMP(n.news_time) + UNIX_TIMESTAMP(NOW())) ) as rank FROM news_table n ORDER BY rank', $votes);
Aside from that, yes you could use usort, but that would also require you to have the entire recordset in memory to provide accurate sorting, which could be problematic at some point.
After getting negative feedback from asking this question in a new question... here is my revised question. Yes it is the same project I am working on, but I was unclear that I needed to basically have a Round Robin type of scheduler.
I'm working on a Round Robin Style Hockey League Scheduler, and need some help.
The overall goal is for the end admin user to be able to punch in 3 variables and have it perform a round Robin style schedule until the WEEKS counter has been hit. Below is an example of the amount of teams and the amount of weeks games are played.
$Teams = array('team1','team2','team3','team4','team5','team6','team7','team8');
$Weeks = 16;
The goal is to have it loop 16 times, making 4 games a week, having each team playing 1 time a week. The round robin algorithm should have teams playing different teams each week until all possibles combinations have been made, but not exceeding 16 weeks. In the event that we only have 4 teams or less teams than possible combinations, we would need to have the round robin start over again until the weeks number was hit.
EDIT:
I am about 90% into what I needed this script to do... but I am stuck on one thing. I need help with merging a multi-dimensional array.
First are the Tiers. Next are the Weeks (all are week 1). Then are the Games for the team match up.
Array
(
[1] => Array
(
[1] => Array
(
[1] => Array
(
[home] => Whalers
[visitor] => Lumberjacks
)
[2] => Array
(
[home] => Team America
[visitor] => Wolfpack
)
)
)
[2] => Array
(
[1] => Array
(
[1] => Array
(
[home] => Warriors
[visitor] => Litchfield Builders
)
[2] => Array
(
[home] => Icemen
[visitor] => Nighthawks
)
)
)
[3] => Array
(
[1] => Array
(
[1] => Array
(
[home] => The Freeze
[visitor] => Devils Rejects
)
[2] => Array
(
[home] => Cobras
[visitor] => New Haven Raiders
)
[3] => Array
(
[home] => Crusaders
[visitor] => Whalers
)
[4] => Array
(
[home] => Blizzard
[visitor] => CT Redlines
)
)
)
)
I want the end result to drop the tier and merge all same weeks games together to look like the following:
Array
(
[1] => Array
(
[1] => Array
(
[home] => Whalers
[visitor] => Lumberjacks
)
[2] => Array
(
[home] => Team America
[visitor] => Wolfpack
)
[3] => Array
(
[home] => Warriors
[visitor] => Litchfield Builders
)
[4] => Array
(
[home] => Icemen
[visitor] => Nighthawks
)
[5] => Array
(
[home] => The Freeze
[visitor] => Devils Rejects
)
[6] => Array
(
[home] => Cobras
[visitor] => New Haven Raiders
)
[6] => Array
(
[home] => Crusaders
[visitor] => Whalers
)
[8] => Array
(
[home] => Blizzard
[visitor] => CT Redlines
)
)
)
Maybe something like this?
<?php
$teams = array(
'Team 1',
'Team 2',
'Team 3',
'Team 4',
'Team 5',
'Team 6',
'Team 7',
'Team 8'
);
function getMatches($teams) {
shuffle($teams);
return call_user_func_array('array_combine', array_chunk($teams, sizeof($teams) / 2));
}
for ($i = 0; $i < 14; $i += 1) {
print_r(getMatches($teams));
}
I didn't really get how you define the schedule, so if you can explain this a bit, I'll try to help.
Pop one off, randomize, pop another. There's your game. If one is left over, some random team has to be a workhorse and play two games this week:
for ($week=1; $i<=$totalWeeksPlayed; $i++)
{
$games = 0;
$temp = $teams;
while (count($temp) > 1)
{
$team = array_shift($temp);
shuffle($temp);
$opponent = array_shift($temp);
$game[$week][$games] = $team . ' vs' . $opponent;
$games++;
}
if (count($temp) == 1)
{
$workhorses = $teams;
unset($workhorses[array_search($temp[0], $teams));
shuffle($workhorses);
$team = $temp[0];
$opponent = array_shift($workhorses);
$game[$week][$games] = $team . ' vs' . $opponent;
$games++;
}
}
Question below copied from above.
Correct me if I get this wrong, but if all teams have to play on the same regular basis, is it even possible to have all teams play the same amount of matches, if there is an odd number of teams? – Yoshi May 3 '11 at 15:05
Michelle,
The number of teams you are trying to pair-up (in this case 8 teams for 16 weeks) can be a daunting task, and is just the beginning of the "scheduling process". Once the correct, balanced team pairings have been determined, it's just the beginning of putting a schedule together for distribution. Next, a list of the 4 weekly time slots includes the; day of the week, start time and location name for each time slot for the whole 16 week season. Comment: What would be most helpful for you is to get an 8 team scheduling matrix that has balanced opponent, and home & away status. It makes a big difference in the quality of a schedule. It's important to evenly distribute early and late time slots, equal home & away status, and equal team distribution with opponents. Most of the balance is accomplished by using a balanced team pair matrix.
After 35 years of teaching, coaching and scheduling sports in the Boston area, I can offer the following information. Creating league or game schedules for sports organizations seems to be a never ending task shared by many and is repeated over and over as the ages of the participants grow and those running the leagues change, the learning curve associated with creating schedules is significant for those who take over.
With that said, I am amazed at how many highly educated mathematical wizards are involved with trying to come up with the perfect solution (algorithm) that will solve one's scheduling problem. Myself and a friend (who is a mathematical/programmer genius) over a period of 3 years created software that perfectly balances all the important components when creating schedules for 4 to 22 teams. What we learned is that there is no algorithm that can handle all the possible variables that are added to the normal variables to create balanced schedules. What I am saying is there are just as many "what ifs" as there are mathematical permutations and combinations that deal with just creating a team matrix listing opponents and home & visitor status of games.
For example: Let's create a perfectly balanced schedule for an 9 team division playing 4 games a week. After nine weeks all teams have played played 8 games, all have had 1 bye, all have played two times in each of the 4 time slots, and all have been scheduled as the home team 4 times and the visitor team 4 times.
What more could anybody want? Well now comes the fun. Because the 4 time slots you chose has 2 games each Saturday, and 2 games each Sunday, the first problem pops up (after the schedules are created, published and passed out, when 2 coaches from 2 different teams call and say they work Saturdays, can you move our games to Sundays? Sure, I can do that. I'll just make the changes, re-publish and re-distribute the schedules.
After the new schedules are distributed, several days later, a different coach calls up and says, "Hey, you moved some of my games to Saturday, I work Saturdays.., move them back". The phone rings again. This time a it's a coach from another team and says they are in a tournament the 5th week of the schedule and can't play that week. Finally the last call comes in from yet another coach. He says the parents of one of his players teaches CCD classes Sunday afternoons, and half of his team is in the CCD class and wants to move all our Sunday games to Saturday. Enough!
My point is no matter what you do or how perfect a schedule is, the best solution is to find out as many of the player/coach team limitations or restrictions before you assign the playing days and time slots to any schedule. These unpredictable variables makes a mess out of a perfect schedule. People do get angry and complain when undesirable schedules are distributed. When a schedule is bad enough, some parents won't sign their youngster to play the following year. This happens when you have a young family with two or three small children, and Dad's work limits his ability to be there. When there is an early morning game, I think you can see the difficulty for Mom's when it all falls on her shoulders.
For those that are new to scheduling, stay with it. It gets better over time after you gain
a little experience dealing with the problems. If you purchase a scheduling software program to calculate team pairs, be careful. Insist on seeing a full single round robin of the schedule they create. Check for the things described above (about balance and distribution).
Bob R
Given a table of teams, say
team (
teamname
);
And a table of fixtures
fixture (
date;
);
With the relationship decomposed by 'playing'
playing (
fixture_date.
teamname
);
Then it's would simply be a matter of iterating through each date, then team and selecting a team at random who does not already have a fixture for that date, and who have not played the selected team (or not played the selected team recently).
Although a simpler solution would be to have team[n] (where n is 0....number of teams -1) play team[(n+(number of teams)) % X] for varying values of X.