I need help with an algorithm to allocate rooms to people. I need to shuffle through a list of rooms to assign a room to a person and after the fourth person is assigned to the room, it should be out of the list(should be unavailable). I have not had any luck with PHP shuffling and sorting functions yet.
Any help (pointers, references, etc.) will greatly be appreciated.
This sounds like both a homework assignment and a problem with thousands of solutions.
As Jack already said, try to develop your algorithm before you try to implement it in code. If you're having trouble understanding this, then just imagine the scenario and try to write simple rules for solving it in real life
Let's say I have a hallway with 10 rooms. Each room can support up to 4 people. Then 35 people show up and want to be assigned to rooms. How would I do this?
I would probably start at the beginning of the hallway and fill up the first room, then fill up the next room, and continue until I ran out of people or rooms (whichever came first). This solution has the advantage of not having to walk very far down the hallway, but the disadvantage of crowding 4 people into one room instead of spreading them evenly.
If you chose to spread them evenly then you'd want to put one in the first room, then one in the second room, etc. Do this until you run out of rooms, then go back through every room putting a second person.
Since you have two methods of doing this, the next step is determining how to represent your basic components in code. You need a way to represent a room and a way to represent a person. The most important feature of a room is that it can hold up to four people, but it always has to know how many people it's holding. Therefore it might be good to think of each room as it's own number, the number telling you how many people are already in that room.
In PHP, if you want several numbers which are related and ordered (as the rooms in a hallway would be) you use an array. This is true when you have several of ANY variable that need to be related to one another and maintain their respective order.
$rooms = array(0, 0, 0, 0, 0); // 5 rooms, all with 0 people in them
If your room needs to know who is staying in the room (if they need to differentiate between different guests) then you might consider each individual room as a 4-element array, each element of which represents a person and is able to identify that person.
$rooms = array(
array(null, null, null, null),
array(null, null, null, null),
array(null, null, null, null),
array(null, null, null, null),
array(null, null, null, null)
);
After you've created your rooms, you have a certain number of people who are waiting to enter a room. Since each person only counts as 1, unless you need to remember extra information about each person (such as their name, age, etc.) then you should be able to see a very convenient way of representing people.
$peopleNotInARoomYet = 10;
If you do need to remember extra information about these people (suppose after assigning rooms you are asked the question "In which room is Bob?") then you would need something much more intelligent. Perhaps an array of classes, each class representing a person. Unless, of course, the person need only be represented by a single variable. Then you could boil them doing to just an array.
$peopleNotInARoomYet = array("Bob", "Sally", "Bill", "Mary");
//or
$peopleNotInARoomYet = array(new Person('Bob'), new Person('Sally'), new Person('Bill'), new Person('Mary'));
Note that the second method required the Person class already be defined, and it can get very convoluted if you aren't careful.
Once you have a way of representing the people, you want to loop through every person to find them a room. If people are represented by a single number, this would look like:
while($peopleNotInARoomYet > 0){
// Assign a room
$peopleNotInARoomYet--;
}
If the people are represented as an array, you might want to consider the PHP array_pop() function.
PHP.net array_pop manual
$nextPerson = array_pop($peopleNotInARoomYet){
while($nextPerson != NULL){
// Assign a room
$nextPerson = array_pop($peopleNotInARoomYet);
}
Having a way to represent rooms and people now, your new task is to take our definition of assignment from above and turn it into code. So let's look at our first (and probably simpler) method. We put people into a room until it's full, then go to the next room.
This actually poses a few algorithmic challenges. First, how do you "put someone into a room"?
The answer to this actually depends on which definition of a room we used. If we simply had an array of integers (the case when we care how many people are in a room but do not care who is in the room) then we need to add one to this integer.
If we had an array of 4-element arrays, then we need to find the first empty element and place the person in that element. Putting a person into a non-empty element will replace the person who was there before.
Since it's obvious from this first attempt to convert our rule set to code that the method will differ depending on the implementation chosen, for the rest of the example I will assume that rooms need only know how many people are in them, and not who is in them in specific. If you do need this further information, that's a lesson left up to the reader to figure out on their own.
So here's our definition of a room, a person, and the beginning of our loop:
<?php
$rooms = array(0, 0, 0, 0, 0); // 5 empty rooms
$peopleNotInRoomsYet = 14; // We'll choose a number that won't divide evenly
while($peopleNotInRoomsYet > 0){
// Assign a room
$peopleNotInRoomsYet --;
}
?>
And we know that once we've chosen which room to put someone in, actually putting them there involves the following:
$rooms[$roomNumber]++;
So now let's go back to our original problem definition and see where we left off. We want to fill up the first room, then the second, then the third, etc.
So first we needed a way to define filling up a room. Well filling a room involves putting people in it (done) until it's full (not done). So now we need a way to check and see if a room is full.
if($rooms[$roomNumber] == 4)
should do nicely. So now we have a way of "filling a room until it's full"... But after this we need to start on the next room. This means we need to always know which room we are currently filling. If there's something new that we need to know, we need a new variable for it. Let's call this variable $roomNumber. We'll start with $roomNumber = 0; (the first room in the array) and continue until it's 5. Since there is no sixth room (element 5) then if $roomNumber ever becomes 5, we have too many people.
<?php
$rooms = array(0, 0, 0, 0, 0); // 5 empty rooms
$peopleNotInRoomsYet = 14; // We'll choose a number that won't divide evenly
$roomNumber = 0;
while($peopleNotInRoomsYet > 0){ // If there are people waiting...
if($rooms[$roomNumber] == 4){ // If the room is full...
$roomNumber ++; // Go to the next room
}
if($roomNumber == 5){ // If we are out of rooms...
die("Too many people!!!"); // die
}
$rooms[$roomNumber] ++; // Otherwise, add someone to the room
$peopleNotInRoomsYet --; // And remove them from the waiting list
}
?>
I know this was extremely length for the tiny amount of code we ended with, but I was attempting to explain the process of going from concept to code. Recognize the simple rules that you need to follow, then recognize the items you need to keep track of. Also remember that the solution I posted works only in the simplest situation, that being you don't need to know who is in the room, just how many people. It also assumes that no room starts with anyone in it. If room two started out full then this algorithm would fall apart since I didn't check for that. There are plenty of modifications left to be done by the reader, but this should get you pointed in the right direction.
This is not directly a PHP question, you should first describe the algorithm and then try to implement it.
From what I understand the best solution would be to keep a list of rooms sorted by actual empty spaces (so at the beginning you will have empty rooms, then rooms with 1 people, then rooms with 2, etcetera).
Then whenever a new user comes you have to choose a policy to assign it to a room, for example you could choose to always add the newcomer to a most empty room (so you will choose a random one between the empty ones, or between the rooms with just 1 people if no empty one is available and so on).. or you could decide to assign it to one random room which is not full. It depends on what you really need to do.
In anycase you could keep 2 lists of rooms, one for the not-full and one for the full rooms. When a room becomes full because a newcomer fills it you just swap it to the other list. When, instead, an user leaves a full room you switch it back to original list.
Of course keeping the not-full room list ordered by empty spaces is useful just when you policy needs to perform a choice that depends on how many empty spaces there are in any room, otherwise you could just keep all of them together and choose one randomly.
Related
I've got a table with 1000 recipes in it, each recipe has calories, protein, carbs and fat values associated with it.
I need to figure out an algorithm in PHP that will allow me to specify value ranges for calories, protein, carbs and fat as well as dictating the number of recipes in each permutation. Something like:
getPermutations($recipes, $lowCal, $highCal, $lowProt, $highProt, $lowCarb, $highCarb, $lowFat, $highFat, $countRecipes)
The end goal is allowing a user to input their calorie/protein/carb/fat goals for the day (as a range, 1500-1600 calories for example), as well as how many meals they would like to eat (count of recipes in each set) and returning all the different meal combinations that fit their goals.
I've tried this previously by populating a table with every possible combination (see: Best way to create Combination of records (Order does not matter, no repetition allowed) in mySQL tables ) and querying it with the range limits, however that proved not to be efficient as I end up with billions of records to scan through and it takes an indefinite amount of time.
I've found some permutation algorithms that are close to what I need, but don't have the value range restraint for calories/protein/carbs/fat that I'm looking for (see: Create fixed length non-repeating permutation of larger set) I'm at a loss at this point when it comes to this type of logic/math, so any help is MUCH appreciated.
Based on some comment clarification, I can suggest one way to go about it. Specifically, this is my "try the simplest thing that could possibly work" approach to a problem that is potentially quite tricky.
First, the tricky part is that the sum of all meals has to be in a certain range, but SQL does not have a built-in feature that I'm aware of that does specifically what you want in one pass; that's ok, though, as we can just implement this functionality in PHP instead.
So lets say you request 5 meals that will total 2000 calories - we leave the other variables aside for simplicity, but they will work the same way. We then calculate that the 'average' meal is 2000/5=400 calories, but obviously any one meal could be over or under that amount. I'm no dietician, but I assume you'll want no meal that takes up more than 1.25x-2x the average meal size, so we can restrict out initial query to this amount.
$maxCalPerMeal = ($highCal / $countRecipes) * 1.5;
$mealPlanCaloriesRemaining = $highCal; # more on this one in a minute
We then request 1 random meal which is less than $maxCalPerMeal, and 'save' it as our first meal. We then subtract its actual calorie count from $mealPlanCaloriesRemaining. We now recalculate:
$maxCalPerMeal = ($highCal / $countRecipesRemaining) * 1.5); # 1.5 being a maximum deviation from average multiple
Now the next query will ask for both a random meal that is less than $maxCalPerMeal AND $mealPlanCaloriesRemaining, AND NOT one of the meals you already have saved in this particular meal plan option (thus ensuring unique meals - no mac'n'cheese for breakfast, lunch, and dinner!). And we update the variables as in the last query, until you reach the end. For the last meal requested it we don't care about the average and it's associated multiple, as thanks to a compound query you'll get what you want anyway and don't need to complicate your control loops.
Assuming the worst case with the 5 meal 2000 calorie max diet:
Meal 1: 600 calories
Meal 2: 437
Meal 3: 381
Meal 4: 301
Meal 5: 281
Or something like that, and in most cases you'll get something a bit nicer and more random. But in the worst-case it still works! Now this actually just plain works for the usual case. Adding more maximums like for fat and protein, etc, is easy, so lets deal with the lows next.
All we need to do to support "minimum calories per day" is add another set of averages, as such:
$minCalPerMeal = ($lowCal / $countRecipes) * .5 # this time our multiplier is less than one, as we allow for meals to be bigger than average we must allow them to be smaller as well
And you restrict the query to being greater than this calculated minimum, recalculating with each loop, and happiness naturally ensues.
Finally we must deal with the degenerate case - what if using this method you end up needing a meal that is to small or too big to fill the last slot? Well, you can handle this a number of ways. Here's what I'd recommended.
The easiest is just returning less than the desired amount of meals, but this might be unacceptable. You could also have special low calorie meals that, due to the minimum average dietary content, would only be likely to be returned if someone really had to squeeze in a light meal to make the plan work. I rather like this solution.
The second easiest is throw out the meal plan you have so far and regenerate from scratch; it might work this time, or it just might not, so you'll need a control loop to make sure you don't get into an infinite work-intensive loop.
The least easy, requires a control loop max iteration again, but here you use a specific strategy to try to get a more acceptable meal plan. In this you take the optional meal with the highest value that is exceeding your dietary limits and throw it out, then try pulling a smaller meal - perhaps one that is no greater than the new calculated average. It might make the plan as a whole work, or you might go over value on another plan, forcing you back into a loop that could be unresolvable - or it might just take a few dozen iterations to get one that works.
Though this sounds like a lot when writing it out, even a very slow computer should be able to churn out hundreds of thousands of suggested meal plans every few seconds without pausing. Your database will be under very little strain even if you have millions of recipes to choose from, and the meal plans you return will be as random as it gets. It would also be easy to make certain multiple suggested meal plans are not duplicates with a simple comparison and another call or two for an extra meal plan to be generated - without fear of noticeable delay!
By breaking things down to small steps with minimal mathematical overhead a daunting task becomes manageable - and you don't even need a degree in mathematics to figure it out :)
(As an aside, I think you have a very nice website built there, so no worries!)
i am developing a board game in php and now i have problems in writing an algorithm...
the game board is a multidimensional array ($board[10][10]) to define rows and columns of the board matrix or vector...
now i have to loop through the complete board but with a dynamic start point. for example the user selects cell [5,6] this is the start point for the loop. goal is to find all available board cells around the selected cell to find the target cells for a move method. i think i need a performant and efficient way to do this. does anyone know an algorithm to loop through a matrix/vector, only ones every field to find the available and used cells?
extra rule...
in the picture appended is a blue field selected (is a little bigger than the other). the available fields are only on the right side. the left side are available but not reachable from the current selected position... i think this is a extra information which makes the algorithm a little bit complicated....
big thx so far!
kind regards
not completely sure that I got the requirements right, so let me restate them:
You want an efficient algorithm to loop through all elements of an nxn matrix with n approximately 10, which starts at a given element (i,j) and is ordered by distance from (i,j)!?
I'd loop through a distance variable d from 0 to n/2
then for each value of d loop for l through -(2*d) to +(2*d)-1
pick the the cells (i+d, j+l), if i>=0 also pick (i+l,j-d),(i+l, j+d)
for each cell you have to apply a modulo n, to map negativ indexes back to the matrix.
This considers the matrix basically a torus, glueing upper and lower edge as well as left and right edge together.
If you don't like that you can let run d up to n and instead of a modulo operation just ignore values outside the matrix.
These aproaches give you the fields directly in the correct order. For small fields I do doubt any kind of optimization on this level has much of an effect in most situations, Nicholas approach might be just as good.
Update
I slightly modified the cells to pick in order to honor the rule 'only consider fields that are right from the current column or on the same column'
If your map is only 10x10, I'd loop through from [0][0], collecting all the possible spaces for the player to move, then grade the spaces by distance to current player position. N is small, so the fact that the algorithm has O(N^2) shouldn't affect your performance much.
Maybe someone with more background in algorithms has something up their sleeve.
So I have a multidimensional array, containing 36 arrays.
I want for a page to display an "Item of the day" in the way that each array is only available for a specific date, and then not again until all the other array have been featured on other days first. The collection doesn't necessarily need to be randomized.
This is not a duplicate of this because in my case I cannot edit or even do direct queries on any database, nor can I write to files or anything else to permanently mark that an item has been featured.
I can however store perhaps a variable containing the initiation date, which could be used as a reference point to somehow calculate which array should be displayed on the current date.
However, math was never my strong suite so I don't know how to do such very very complex calculations. Nor do I have the programming heft to know if this concept is even the best one.
What do you suggest?
I'm not sure if this counts, but you could do modulo operation on the current date:
$messages = array('hi, how are you', 'nice weather eh?', 'get lost!');
$idx = floor(time() / 86400) % count($messages);
echo $messages[$idx];
Every day it will pick one for that whole day; then move to the next, etc. At the end it goes back to the beginning.
It's sort of random what the first item will be, but after that it's sequential.
I'd reckon you'd have to store the data outside of the program/page code, in order to check against it (dates, etc) each time/each day you load the page.
I have a tricky question that I've looked into a couple of times without figuring it out.
Some backstory: I am making a textbased RPG-game where players fight against animals/monsters etc. It works like any other game where you hit a number of hitpoints on each other every round.
The problem: I am using the random-function in php to generate the final value of the hit, depending on levels, armor and such. But I'd like the higher values (like the max hit) to appear less often than the lower values.
This is an example-graph:
How can I reproduce something like this using PHP and the rand-function? When typing rand(1,100) every number has an equal chance of being picked.
My idea is this: Make a 2nd degree (or quadratic function) and use the random number (x) to do the calculation.
Would this work like I want?
The question is a bit tricky, please let me know if you'd like more information and details.
Please, look at this beatiful article:
http://www.redblobgames.com/articles/probability/damage-rolls.html
There are interactive diagrams considering dice rolling and percentage of results.
This should be very usefull for you.
Pay attention to this kind of rolling random number:
roll1 = rollDice(2, 12);
roll2 = rollDice(2, 12);
damage = min(roll1, roll2);
This should give you what you look for.
OK, here's my idea :
Let's say you've got an array of elements (a,b,c,d) and you won't to randomly pick one of them. Doing a rand(1,4) to get the random element index, would mean that all elements have an equal chance to appear. (25%)
Now, let's say we take this array : (a,b,c,d,d).
Here we still have 4 elements, but not every one of them has equal chances to appear.
a,b,c : 20%
d : 40%
Or, let's take this array :
(1,2,3,...,97,97,97,98,98,98,99,99,99,100,100,100,100)
Hint : This way you won't only bias the random number generation algorithm, but you'll actually set the desired probability of apparition of each one (or of a range of numbers).
So, that's how I would go about that :
If you want numbers from 1 to 100 (with higher numbers appearing more frequently, get a random number from 1 to 1000 and associate it with a wider range. E.g.
rand = 800-1000 => rand/10 (80->100)
rand = 600-800 => rand/9 (66->88)
...
Or something like that. (You could use any math operation you imagine, modulo or whatever... and play with your algorithm). I hope you get my idea.
Good luck! :-)
So recently I was given a problem, which I have been mulling over and am still unable to solve; I was wondering if anyone here could point me in the right direction by providing me with the psuedo code (or at least a rough outline of the pseudo code) for this problem. PS I'll be building in PHP if that makes a difference...
Specs
There are ~50 people (for this example I'll just call them a,b,c... ) and the user is going to group them into groups of three (people in the groups may overlap), and in the end there will be 50-100 groups (ie {a,b,c}; {d,e,f}; {a,d,f}; {b,c,l}...). *
So far it is easy, it is a matter of building an html form and processing it into a multidimensional array
There are ~15 time slots during the day (eg 9:00AM, 9:20AM, 9:40AM...). Each of these groups needs to meet once during the day. And during one time slot the person cannot be double booked (ie 'a' cannot be in 2 different groups at 9:40AM).
It gets tricky here, but not impossible, my best guess at how to do this would be to brute force it (pick out sets of groups that have no overlap (eg {a,b,c}; {l,f,g}; {q,n,d}...) and then just put each into a time slot
Finally, the schedule which I output needs to be 'optimized', by that I mean that 'a' should have minimal time between meetings (so if his first meeting is at 9:20AM, his second meeting shouldn't be at 2:00PM).
Here's where I am lost, my only guess would be to build many, many schedules and then rank them based on the average waiting time a person has from one meeting to the next
However My 'solutions' (I hesitate to call them that) require too much brute force and would take too long to create. Are there simpler, more elegant solutions?
These are the table laid out, modified for your scenerio
+----User_Details------+ //You may or may not need this
| UID | Particulars... |
+----------------------+
+----User_Timeslots---------+ //Time slots per collumn
| UID | SlotNumber(bool)... | //true/false if the user is avaliable
+---------------------------+ //SlotNumber is replaced by s1, s2, etc
+----User_Arrangements--------+ //Time slots per collumn
| UID | SlotNumber(string)... | //Group session string
+-----------------------------+
Note: That the string in the Arrangement table, was in the following format : JSON
'[12,15,32]' //From SMALLEST to BIGGEST!
So what happens in the arrangement table, was that a script [Or an EXCEL column formula] would go through each slot per session, and randomly create a possible session. Checking all previous sessions for conflicts.
/**
* Randomise a session, in which data is not yet set
**/
function randomizeSession( sesionID ) {
for( var id = [lowest UID], id < [highest UID], id++ ) {
if( id exists ) {
randomizeSingleSession( id, sessionID );
} //else skips
}
}
/**
* Randomizes a single user in a session, without conflicts in previous sessions
**/
function randomizeSingleSession( id, sessionID ) {
convert sessionID to its collumns name =)
get the collumns name of all ther previous session
if( there is data, false, or JSON ) {
Does nothing (Already has data)
}
if( ID is avaliable in time slot table (for this session) ) {
Get all IDs who are avaliable, and contains no data this session
Get all the UID previous session
while( first time || not yet resolved ) {
Randomly chose 2
if( there was conflict in UID previous session ) {
try again (while) : not yet resolved
} else {
resolved
}
}
Registers all 3 users as a group in the session
} else {
Set session result to false (no attendance)
}
}
You will realize the main part of the assignment of groups is via randomization. However, as the amount of sessions increases. There will be more and more data to check against for conflicts. Resulting to a much slower performance. However large being, ridiculously large, to an almost perfect permutation/combination formulation.
EDIT:
This setup will also help ensure, that as long as the user is available, they will be in a group. Though you may have pockets of users, having no user group (a small number). These are usually remedied by recalculating (for small session numbers). Or just manually group them together, even if it is a repeat. (having a few here and there does not hurt). Or alternatively in your case, along with the remainders, join several groups of 3's to form groups of 4. =)
And if this can work for EXCEL with about 100+ ppl, and about 10 sessions. I do not see how this would not work in SQL + PHP. Just that the calculations may actually take some considerable time both ways.
Okay, for those who just join in on this post, please read through all the comments to the question before considering the contents of this answer, as this will very likely fly over your head.
Here is some pseudo code in PHP'ish style:
/* Array with profs (this is one dimensional here for the show, but I assume
it will be multi-dimensional, filled with availability and what not;
For the sake of this example, let me say that the multi-dimensional array
contains the following keys: [id]{[avail_from],[avail_to],[last_ses],[name]}*/
$profs = array_fill(0, $prof_num, "assoc_ids");
// Array with time slots, let's say UNIX stamps of begin time
$times = array_fill(0, $slot_num, "time");
// First, we need to loop through all the time slots
foreach ($times as $slot) {
// See when session ends
$slot_end = $slot + $session_time;
// Now, run through the profs to see who's available
$avail_profs = array(); // Empty
foreach ($profs as $prof_id => $data) {
if (($data['avail_from'] >= $slot) && ($data['avail_to'] >= $slot_end)) {
$avail_prof[$prof_id] = $data['last_ses'];
}
}
/* Reverse sort the array so that the highest numbers (profs who have been
waiting the longest) will be up top */
arsort($avail_profs);
$profs_session = array_slice($avail_profs, 0, 3);
$profs_session_names = array(); // Empty
// Reset the last_ses counters on those profs
foreach ($profs_session as $prof_id => $last_ses) {
$profs[$prof_id]['last_ses'] = 0;
$profs_session_names[0] = $profs[$prof_id]['name'];
}
// Now, loop through all profs to add one to their waiting time
foreach ($profs as $prof_id = > $data) {
$profs[$prof_id]['last_ses']++;
}
print(sprintf('The %s session will be held by: %s, $s, and %s<br />', $slot,
$profs_session_names[0], $profs_session_names[1],
$profs_session_names[2]);
unset ($profs_session, $profs_session_names, $avail_prof);
}
That should print something like:
The 9:40am session will be held by: C. Hicks, A. Hole, and B.E.N. Dover
I see an object model consisting of:
Panelists: a fixed repository of of your the panelists (Tom, Dick, Harry, etc)
Panel: consists of X Panelists (X=3 in your case)
Timeslots: a fixed repository of your time slots. Assuming fixed duration and only occurring on a single day, then all you need is track is start time.
Meeting: consists of a Panel and Timeslot
Schedule: consists of many Meetings
Now, as you have observed, the optimization is the key. To me the question is: "Optimized with respect to what criteria?". Optimal for Tom might means that the Panels on which he is a member lay out without big gaps. But Harry's Panels may be all over the board. So, perhaps for a given Schedule, we compute something like totalMemberDeadTime (= sum of all dead time member gaps in the Schedule). An optimal Schedule is the one that is minimal with respect to this sum
If we are interested in computing a technically optimal schedule among the universe of all schedules, I don't really see an alternative to brute force .
Perhaps that universe of Schedules does not need to be as big as might first appear. It sounds like the panels are constituted first and then the issue is to assign them to Meetings which them constitute a schedule. So, we removed the variability in the panel composition; the full scope of variability is in the Meetings and the Schedule. Still, sure seems like a lot of variability there.
But perhaps optimal with respect to all possible Schedules is more than we really need.
Might we define a Schedule as acceptable if no panelist has total dead time more than X? Or failing that, if no more than X panelists have dead time more than X (can't satisfy everyone, but keep the screwing down to a minimum)? Then the user could assign meeting for panels containing the the more "important" panelists first, and less-important guys simply have to take what they get. Then all we have to do is fine a single acceptable Schedule
Might it be sufficient for your purposes to compare any two Schedules? Combined with an interface (I'm seeing a drag-and-drop interface, but that's clearly beyond the point) that allows the user to constitute a schedule, clone it into a second schedule, and tweak the second one, looking to reduce aggregate dead time until we can find one that is acceptable.
Anyway, not a complete answer. Just thinking out loud. Hope it helps.