Math: Pallet Packing (not quite the bin-packing situation) - javascript

I'm currently working on some private project on my spare time, and I've been stuck on a particular math problem.
I know that bin packing is a NP-HARD problem, but that's not exactly the problem I'm facing here.
What I must do is to calculate the number of pallets I would need to fit the given number of boxes, however the layout (of first/base level) is defined in advance. For me, the problem is that I have to take the box weight, pallet max weight, box height and pallet max height into consideration. In one moment it sounds like an elementary school math, but then I suddenly get lost with too many if and else statements.
The following is what I have:
Pallet (width, depth, max. height, max. weight)
Box ( total number of boxes, width, depth, height, weight)
Now, as I mentioned, the easy part is that I already know in advance how many boxes can I fit on a first layer. But then, I get lost because I'm trying to check too many things.
In example, the pallet can reach it's max weight before the max height is filled (and vice versa). Another (improbable but possible) scenario, the total number of boxes (if small enough) can be fitted on a single pallet without reaching the pallet's max height/weight.
In the end, I need to know the number of fully loaded pallets and if any boxes left for the last (partially filled) pallet.
I'm currently working in javascript. I'd be grateful if anyone can help me with with this, at least with some pseudo code that I can convert.
If you're willing to give it a shot, here are some numbers you can run your algorithm against:
Box Values:
+-----------+-----------+------------+------------+--------------------+-------------------+
| Width(cm) | Depth(cm) | Height(cm) | Weight(kg) | Fits(single layer) | Total (number of) |
+-----------+-----------+------------+------------+--------------------+-------------------+
| 32.5 | 24 | 22 | 14.7 | 9 | 111 |
+-----------+-----------+------------+------------+--------------------+-------------------+
Pallet Values:
+-----------+-----------+----------------+----------------+
| Width(cm) | Depth(cm) | Max.Height(cm) | Max.Weight(cm) |
+-----------+-----------+----------------+----------------+
| 120 | 80 | 145 | 725 |
+-----------+-----------+----------------+----------------+
EDIT:
I apologize for being unclear. I was lost in my own calculations. I updated the given values.
Also, I forgot to mention that the pallets are loaded to fill max in both weight and height. So, there can also be a partially filled layer on top (in case that max height isn't filled with previous layers and one more filled layer would go over allowed max weight).

OK, here is the code that I think embodies what meowgoesthedog meant:
function fillPallets(totalBoxNumber, boxHeight, boxWeight, boxesPerLayer, palletMaxHeight, palletMaxWeight) {
let maxBoxesByHeight = Math.floor(palletMaxHeight / boxHeight) * boxesPerLayer;
let maxBoxesByWeight = Math.floor(palletMaxWeight / boxWeight);
let maxBoxesPerPallet = Math.min(maxBoxesByHeight, maxBoxesByWeight);
let fullPalletsCount = Math.floor(totalBoxNumber / maxBoxesPerPallet);
let palletsCount = Math.ceil(totalBoxNumber / maxBoxesPerPallet);
let lastPalletBoxes = totalBoxNumber - fullPalletsCount * maxBoxesPerPallet;
let buildPallet = function (number) {
let palletLayers = [];
for (let rest = number; rest > 0; rest -= boxesPerLayer) {
palletLayers.push(Math.min(rest, boxesPerLayer))
}
return palletLayers;
}
let pallets = [];
let fullPallet = buildPallet(maxBoxesPerPallet);
for (let i = 0; i < fullPalletsCount; i++) {
pallets.push(fullPallet);
}
if (lastPalletBoxes > 0)
pallets.push(buildPallet(lastPalletBoxes));
return {
count: palletsCount,
palletsLayouts: pallets
}
}
Usage example
fillPallets(111,22,14.7,9,145,725)
produces following output
{
"count": 3,
"palletsLayouts": [[9, 9, 9, 9, 9, 4], [9, 9, 9, 9, 9, 4], [9, 4]]
}
The idea behind this code is that you want to calculate maxBoxesPerPallet and there are only two independent restrictions on that: either height or weight. So you calculate maxBoxesByHeight and maxBoxesByWeight first, then you get maxBoxesPerPallet, then you get the number of pallets (most of them will be "full" i.e. contain exactly maxBoxesPerPallet and there might be one last for the rest).

Related

How to create a quantity discount formula using Math JavaScript?

I am learning JavaScript for WordPress Websites, but I am only asked for the formula without including HTML or CSS fields, I just need the JS.
I need to create a formula to take a quantity [quantity] multiply it by the base price [base price] and then subtract the [discount] based on the following list they give me.
If you buy:
1 = 4
2 to 10 = 3.4
11 to 50 = 3.0
From 51 to 100 = 2.75
From 101 to 1000 = 2.50
This is the code I have been able to create but it only gives me the price until it reaches 10.
It is an exercise or an example, because I am learning how to create the code... the goal is to make the list above.
(function(){
if([field id="quantity"]<=10)
{
return 4;
}
else if(10>[field id="quantity"] && [field id="quantity"]<=20)
{
return 3.5;
}
else
{
return 0;
}
})() * [field id="quantity"]
Here is an example I made to give you an idea of the structure:
Loan Calculator Form | Monthly Payment:
[field id="amount"]*([field id="annual_interest"]/1200)/(1-(Math.pow(1/(1+([field id="annual_interest"]/1200)),([field id="repayment_period"]*12))))
List of available static mathematical methods I was given to use
Url of this list: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math?retiredLocale=vi#static_methods
I would like you to show me some examples of quantity discounts to learn how to handle the tools involved in performing this equation.

Divide 100 in equal percentages in JavaScript

Ok. I will try to explain my case as good as I can.
Basically, I have this function here that will add an increment to my bootstrap progressbar:
function progress(increment) {
f = parseInt(document.getElementsByClassName('progress-bar')[0].style.width);
document.getElementsByClassName('progress-bar')[0].style.width = (f + increment) +'%';
}
Now for each step, for instance there are 6 steps, I must divide 100 into 6 steps incrementally.
For instance, if we are in step 1: 100 / 6 = 16.67 each so this must add 16.67 incrementally.
So it would be like this:
Step 1: Add 16.67
Step 2: Add 33.34
Step 3: Add 50.01
---- and the list go on
So on my program I have the ff variables:
let current_step = 0;
let stepCount = 6
So if I used the progress function, I tried dividing 100 to stepCount to the current_step:
progress(100 / (stepCount - current_step)
But this did not resolve the issue and I am getting only weird numbers.
Any idea what's the proper formula here to get the right numbers to add on the progressbar?
PS. Sorry, I hope its not that confusing. I tried to explain this at the best I can.
Your calculation isn't correct. This should give you the correct value.
progress((100 / stepCount) * current_step);
function progress(value) {
document.getElementsByClassName('progress-bar')[0].style.width = `${value}%`;
}
or you can do it like this
progress(100 / stepCount);
function progress(increment) {
let f = +document.querySelector('.progress-bar')[0].style.width.slice(0, -1);
// I'm slicing the width to remove the `%`
document.getElementsByClassName('progress-bar')[0].style.width = `${f + increment}%`;
}

Debugging a base conversion calculator up to base 809

So my calculator is built for absolutely huge numbers. I am having a bit of fun here and making a base system based on the pokedex. So for example, base 151 would include all the Pokemon from generation 1, and base 809 goes all the way to Melmetal from Pokemon Go. The number 0 is represented by an unhatched pokemon egg.
I am running into a problem that I cannot figure out how to determine what is wrong, some of these symptoms may be from the same problem but I am unsure.
Symptom 1:
Currently on my screen, I have it set to base pidgey (base 16) and I have input the base 10 number 80000006871717981.
My math gives me the following remainders, with the corresponding representative images
(1)(1)(12)(3)(7)(9)(5)(1)(7)(9)(14)(1)(8)(6)(0)
Bulbasaur Bulbasaur Butterfree Venusaur Squirtle Blastoise Charmeleon Bulbasaur Squirtle Blastoise Kakuna Bulbasaur Wartortle Charizard Egg
And the output from simply converting with toString(16) is 11c3795179e1860.
The output from the windows 10 calculator is ‭11C3795179E185D‬, implying that the e/Kakuna, 6/Charizard, and 0/Egg are wrong.
Symptom 2:
Large numbers will have the same outputs.
1000000000000000001 and 1000000000000000000 both output as
(3)(458)(599)(562)(324)(484)(498)
Venasaur Mantyke Klang Yamask Torkoal Palkia Tepig
I feel like this is relating to the size of the number, but don't know how I can prevent this.
For symptom 2, I have tried casting the value to a BigInt when I set the value for input, but that just gave me nothing, only the output3 got anything calculated
function calculate()
{
document.getElementById('output').innerHTML= '';
document.getElementById('output2').innerHTML= '';
var base = {{base}};//gets base from the python code
var input = document.getElementById('base10').value;
document.getElementById('output3').innerHTML= parseInt(input).toString(16);
var remainder = input % base;
while(input > 0)
{
//alert(input + " % " + base + " = " + remainder);
document.getElementById('output').innerHTML = '<img src="{{url_for('static', filename='images/')}}'+ remainder+'MS.png">' + document.getElementById('output').innerHTML;//adds the images of the pokemon
document.getElementById('output2').innerHTML = '('+remainder+')' + document.getElementById('output2').innerHTML;
input = parseInt(input / base);
remainder = input % base;
}
}
For my expected results, as I mentioned the built in js function shows the base 16 test is accurate, but the windows 10 calculator says otherwise. I don't know which one to go with.
And as far as the problems with large numbers go, I just need to make large base 10 numbers stay stable, not get scientific notation, and still be able to be processed.

Where is my logic going wrong in trying to calculate the presidential outcome?

Let me explain what I'm trying to do. I have data like
const dataByState = {
'Washington' : { ElectoralVotes : 12, RChance: 54, DChance: 46 },
'Oregon': { ElectoralVotes: 7, RChance: 51, DChance: 49 },
.
.
.
'Hawaii' : { ElectoralVotes: 4, RChance : 40, DChance: 60 }
};
where one of the above key-value pairs like
'Hawaii' : { ElectoralVotes: 4, RChance : 40, DChance: 60 }
means "In the state Hawaii, which has 4 electoral votes, there is a 40% chance of the Republican Candidate winning and a 60% chance of the Democrat candidate winning". What I'm ultimately trying to do is calculate the chance of each candidate winning the election. How this would be done in a perfect world is
Iterate through all 2^51 combinations of states
For each combination c, its combined electoral votes are greater than or equal to 270, add it to a collection C of collecions of states
For the Republican candidate, sum up the probabilities of winning each combination of states in C; call that value r. That's his/her chance of winning. The Democrat's chance is 1 - r.
But since I can't go through all 2^51, what I'm doing is choosing some N smaller than 51 and doing
Find a random 2^N combinations of states whose combined electoral votes sum to greater than or equal to 270; call this combination C.
For the Republican candidate, sum up the probabilities of winning each combination of states in C; call that value r. Multiply r by 2^(51-N). That's approximately his/her chance of winning. The Democrat's chance is 1 - r.
Anyhow, this doesn't seem to be working and I'm wondering whether my logic is wrong (I haven't taken statistics since college 3 years ago) or if I'm running into rounding errors. I'm getting a near 100% of the Republican winning (i.e. America being made great again) when I make the chance even in every state, which is wrong because it should calculate to about 50/50.
Code dump: https://jsfiddle.net/pqhnwek9/
The probability of a republican victory is
probRepVict = 0
for(combination in combinations) {
if(combination is republican victory) {
probRepVict += proability of combination
}
}
As you observe it is not feasible to calculate the entire sum. Hence, you choose some subset C to try to estimate this probability.
N = number of combination // 2^51
n = size of C
probRepVictEstimate = 0
for(combination in C) {
if(combination is republican victory) {
probRepVictEstimate += proability of combination
}
}
probRepVictEstimate *= N/n
In the last statement we assume that the probability of a victory scales linearly with the size of the subset.
I believe the logic goes wrong at several places in the script:
(1) When generating the random number you might not get a sufficiently many bits of randomness. For instance if there were 54 states you would be outside of the safe integer range. Some implementations might give you even less fewer bits of randomness (it did break for me in Node, which only give 32 bits). Thus I suggest adding a function
function getRandom() {
// Generate 32 random bits
var s = Math.floor(Math.random()*Math.pow(2, 32)).toString(2)
return new Array(32 - s.length + 1).join("0") + s
}
Replacing
const rand = Math.floor(Math.random() * Math.pow(2,states.length));
with const rand = getRandom() + getRandom();, and replace getCombo with
const getCombo = (i) => {
let combo = [];
for(var j = 0; j < states.length; ++j)
if(i[j] == "0")
combo.push(states[j]);
return combo;
}
(2) You need to count both wins and losses for the republican party to be able to estimate the probability. Thus you cannot add the complement of a combo (by the way, ~ is a bitwise operations, hence convert the operand to a 32-bit integer, so your code does not work as intended). Hence your code should be simplified to:
...
if(!winningCombos.hasOwnProperty(rand)) {
const stateCombo = getCombo(rand);
if(hasSufficientVotes(stateCombo))
{
winningCombos[rand] = stateCombo;
++wins;
}
++count;
}
...
(3) You should scale repubChanceSum by N/n, where N = Math.pow(2, 51) and n = limit. Note that limit should be considerably greater than winningCombos.length.
With these modifications the code correctly predicts a ~50% probability. See this modified fiddle.
Let's hope we get a more optimistic outlook for the future with more realistic probabilities.

Custom calendar with event divs

I am working on an event system which is basically a container with 720px height with each pixel representing one minute from 9AM to 9PM and has width of 620px (10px padding from left and right)
The natural requirement for the calendar system is that:
The objects should be laid out so that they do not visually overlap.
If there is one event in a time slot, its width will be 600px
Every colliding event must be the same width as every other event that it collides width.
An event should use the maximum width possible while still adhering to the first constraint.
The input will be an array something like:
[
{id : 1, start : 30, end : 150}, // an event from 9:30am to 11:30am
{id : 2, start : 540, end : 600}, // an event from 6pm to 7pm
{id : 3, start : 560, end : 620}, // an event from 6:20pm to 7:20pm
{id : 4, start : 610, end : 670} // an event from 7:10pm to 8:10pm
]
I have created the needed layout but I am stuck with JavaScript part :( This is what I have so far:
var Calendar = function() {
var layOutDay = function(events) {
var eventsLength = events.length;
if (! eventsLength) return false;
// sort events
events.sort(function(a, b){return a.start - b.start;});
for (var i = 0; i < eventsLength; i++) {
// not sure what is next
}
};
return {
layOutDay : layOutDay,
}
}();
Need to create divs and position them as per above requirements.
Please see JSBin demo.
Any help will be greatly appreciated.
Here is a working solution: http://jsbin.com/igujil/13/edit#preview
As you can see, it's not an easy problem to solve. Let me walk you through how I did it.
The first step, labelled Step 0, is to make sure the events are sorted by id. This will make our lives easier when we start playing with the data.
Step 1 is to initialize a 2-dimensional array of timeslots. For each minute in the calendar, we're going to make an array which will contain the events that take place during that minute. We do that in...
Step 2! You'll note I added a check to make sure the event starts before it ends. A little defensive, but my algorithm would hit an infinite loop on bad data, so I want to make sure the events make sense.
At the end of this loop, our timeslot array will look like this:
0: []
1: []
...
30: [1]
31: [1]
...
(skipping ahead to some interesting numbers)
540: [2]
560: [2,3]
610: [3,4]
I encourage you to add console.log(timeslots) just before Step 3 if you're confused/curious. This is a very important piece of the solution, and the next step is a lot more difficult to explain.
Step 3 is where we resolve scheduling conflicts. Each event needs to know two things:
The maximum number of times it conflicts.
Its horizontal ordering (so that conflicts don't overlap).
(1) is easy because of how our data is stored; the width of each timeslot's array is the number of events. Timeslot 30, for example, has only 1 event, because Event #1 is the only one at that time. At Timeslot 560, however, we have two events, so each event (#2 and #3) gets a count of two. (And if there was a row with three events, they would all get a count of three, etc.)
(2) is a little more subtle. Event #1 is obvious enough, because it can span the entire width of the calendar. Event #2 will have to shrink its width, but it can still start along the left edge. Event #3 can't.
We solve this with a per-timeslot variable I called next_hindex. It starts at 0, because by default we want to position along the left edge, but it will increase each time we find a conflict. That way, the next event (the next piece of our conflict) will start at the next horizontal position.
Step 4 is quite a bit more straightforward. The width calculation uses our max-conflict count from Step 3. If we know we have 2 events at 5:50, for example, we know each event has to be 1/2 the width of the calendar. (If we had 3 events, each would be 1/3, etc.) The x-position is calculated similarly; we're multiplying by the hindex because we want to offset by the width of (number of conflict) events.
Finally, we just create a little DOM, position our event divs, and set a random colour so they're easy to tell apart. The result is (I think) what you were looking for.
If you have any questions, I'd be happy to answer. I know this is probably more code (and more complexity) than you were expecting, but it was a surprisingly complicated problem to solve :)
Here is my solution: # one-day
A one day calendar
Requirements
Part I: Write a function to lay out a series of events on the calendar for a single day.
Events will be placed in a container. The top of the container represents 9am and the bottom represents 9pm.
The width of the container will be 620px (10px padding on the left and right) and the height will be 720px (1 pixel for every minute between 9am and 9pm). The objects should be laid out so that they do not visually overlap. If there is only one event at a given time slot, its width should be 600px.
There are 2 major constraints:
1. Every colliding event must be the same width as every other event that it collides width.
2. An event should use the maximum width possible while still adhering to the first constraint.
See below image for an example.
The input to the function will be an array of event objects with the start and end times of the event. Example (JS):
[
{id : 1, start : 60, end : 120}, // an event from 10am to 11am
{id : 2, start : 100, end : 240}, // an event from 10:40am to 1pm
{id : 3, start : 700, end : 720} // an event from 8:40pm to 9pm
]
The function should return an array of event objects that have the left and top positions set (relative to the top left of the container), in addition to the id, start, and end time.
Part II: Use your function from Part I to create a web page that is styled just like the example image below.
with the following calendar events:
An event that starts at 9:30 am and ends at 11:30 am
An event that starts at 6:00 pm and ends at 7:00pm
An event that starts at 6:20pm and ends at 7:20pm
An event that starts at 7:10pm pm and ends at 8:10 pm
Code
Installation & Running
Clone this repo
Open the index.html file in your favourite browser.
Note: at startup there is a default set of events (required in the second part).
For testing, right below the default array (line 14), you can find generateEvents function which generates random array of events. The array size will be determined by the arrayLength attribute.
Dependencies
none!
Solution
Below you can find the algorithm to solve the problem according to requirements.
Introduction
I will try to address this task in manner of graphs, so few terms should be given.
Terms
Node: represents an event - $n$, $n \in N, N$ - group of all nodes.
Edge: represents colliding events - $e$, $e \in E, E$ - group of all edges. For example, if node $u$ and $v$ collide then there will be an edge $e_{u,v}$ connecting them.
Graph: the collection of nodes and edges $G, G\in(N,E)$ .
Cluster: represents a group of connected nodes ( sub group of the Graph) - $c$, $c \subseteq G$ . For example, if we have the following nodes: $u, v, w$ and edge $e_{u,v}$. Then there will be 2 clusters, the first will contain $u,v$ and the second will contain only $w$.
Clique: represents sub group of nodes in a cluster, each pair of nodes in this group has a connecting edge - $cq$, $cq \subseteq c$. Note, a clique represents a group of colliding events.
Board: The day container which holds all the events.
Terms example
For the following input:
[
{id : 1, start : 0, end : 120},
{id : 2, start : 60, end : 120},
{id : 3, start : 60, end : 180},
{id : 4, start : 150, end : 240},
{id : 5, start : 200, end : 240},
{id : 6, start : 300, end : 420},
{id : 7, start : 360, end : 420},
{id : 8, start : 300, end : 720}
]
The graph will be:
Black cycle - node - event
Green ellipse - clique - group of colliding events
Red ellipse - cluster - group of connected nodes
Blue line - edge - connector between colliding events
Note: the top left green ellipse is the biggest clique in the left cluster.
The board will be:
Red rectangle - cluster
Colored dots - clique (each color is a different clique).
Constraint paraphrase
Each node in the same cluster must have the same width on the board, in order to meet the first constraint.
Nodes must not overlap each other on the board while starching to maximum width and still adhering to the first constraint.
The width of nodes in a cluster will be set by the biggest clique in the cluster. This is true because, nodes in the same clique will share at least one minute on the board, meaning that they will have to have the same width (because they are colliding). So other nodes in the cluster will have the same width as the smallest node.
Each node in a clique will gain its X axis position relative to its neighbours.
Algorithm
For given array of events arrayOfEvents (from the requirements example):
[
{id : 1, start : 60, end : 120}, // an event from 10am to 11am
{id : 2, start : 100, end : 240}, // an event from 10:40am to 1pm
{id : 3, start : 700, end : 720} // an event from 8:40pm to 9pm
]
Step One: creating events histogram.
An Array of arrays will be created, lets call this array as histogram. The histogram length will be 720, each index of the histogram will represent a minute on the board (day).
Lets call each index of the histogram a minute. Each minute, is an array itself. Each index of the minute array represents an event which takes place at this minute.
pseudo code:
histogram = new Array(720);
forEach minute in histogram:
minute = new Array();
forEach event in arrayOfEvents:
forEach minute inBetween event.start and endMinute:
histogram[minute].push(event.id);
histogram array will look like this after this step (for given example):
[
1: [],
2: [],
.
.
.
59: [],
60: [1],
61: [1],
.
.
.
99: [1],
100: [1,2],
101: [1,2],
.
.
.
120: [1,2],
121: [2],
122: [2],
.
.
.
240: [2],
241: [],
242: [],
.
.
.
699: [],
700: [3],
701: [3],
.
.
.
720: [3]
]
Step Two: creating the graph
In this step the graph will be created, including nodes, node neighbours and clusters also the biggest clique of the cluster will be determined.
Note that there won't be an edge entity, each node will hold a map of nodes (key: node id, value: node) which it collides with (its neighbours). This map will be called neighbours. Also, a maxCliqueSize attribute will be added to each node. The maxCliqueSize is the biggest clique the node is part of.
pseudo code:
nodesMap := Map<nodeId, node>;
graph := Object<clusters, nodesMap>;
Node := Object<nodeId, start, end, neighbours, cluster, position, biggestCliqueSize>;
Cluster := Object<mapOfNodesInCluster, width>
//creating the nodes
forEach event in arrayOfEvents {
node = new Node(event.id, event.start, event.end, new Map<nodeId, node>, null)
nodeMap[node.nodeId] = node;
}
//creating the clusters
cluster = null;
forEach minute in histogram {
if(minute.length > 0) {
cluster = cluster || new Cluster(new Array(), 0);
forEach eventId in minute {
if(eventId not in cluster.nodes) {
cluster.nodes[eventId] = nodeMap[eventId];
nodeMap[eventId].cluster = cluster;
}
}
} else {
if(cluster != null) {
graph.clusters.push(cluster);
}
cluster = null;
}
}
//adding edges to nodes and finding biggest clique for each node
forEach minute in histogram {
forEach sourceEventId in minute {
sourceNode = eventsMap[sourceEventId];
sourceNode.biggestCliqueSize = Math.max(sourceNode.biggestCliqueSize, minute.length);
forEach targetEventId in minute {
if(sourceEventId != targetEventId) {
sourceNode.neighbours[targetEventId] = eventsMap[targetEventId];
}
}
}
}
Step Three: calculating the width of each cluster.
As mentioned above, the width of all nodes in the cluster will be determined by the size of the biggest clique in the cluster.
The width of each node $n$ in cluster $c$ will follow this equation:
$$n_{width} = \frac{Board_{width}}{Max\left ( n_{1}.biggestCliqueSize, n_{2}.biggestCliqueSize, ..., n_{n}.biggestCliqueSize\right )}$$
Each node width will be set in the cluster its related to. So the width property will be set on the cluster entity.
pseudo code:
forEach cluster in graph.clusters {
maxCliqueSize = 1;
forEach node in cluster.nodes {
maxCliqueSize = Max(node.biggestCliqueSize, sizeOf(node.clique);
}
cluster.width = BOARD_WIDTH / maxCliqueSize;
cluster.biggestCliqueSize = biggestCliqueSize;
}
Step Four: calculating the node position within its clique.
As already mentioned, nodes will have to share the X axis (the "real-estate") with its neighbours. In this step X axis position will be given for each node according to its neighbours. The biggest clique in the cluster will determine the amount of available places.
pseudo code:
forEach node in nodesMap {
positionArray = new Array(node.cluster.biggestCliqueSize);
forEach cliqueNode in node.clique {
if(cliqueNode.position != null) {
//marking occupied indexes
positionArray[cliqueNode.position] = true;
}
}
forEach index in positionArray {
if(!positionArray[index]) {
node.position = index;
break;
}
}
}
Step Five: Putting nodes on the board.
In this step we already have all the information we need to place an event (node) on its position on the board. The position and size of each node will be determined by:
height: node.end - node.start
width: node.cluster.width
top-offset: node.start
left-offset: node.cluster.width * node.position + left-padding
Algorithm Complexity
The time complexity of the algorithm is $O\left(n^{2} \right )$.
The space complexity of the algorithm is $O\left (n \right )$.
Github repo: https://github.com/vlio20/one-day
if you want to roll your own then use following code:
DEMO: http://jsfiddle.net/CBnJY/11/
var Calendar = function() {
var layOutDay = function(events) {
var eventsLength = events.length;
if (!eventsLength) return false;
// sort events
events.sort(function(a, b) {
return a.start - b.start;
});
$(".timeSlot").each(function(index, val) {
var CurSlot = $(this);
var SlotID = CurSlot.prop("SlotID");
var EventHeight = CurSlot.height() - 1;
//alert(SlotID);
//get events and add to calendar
var CurrEvent = [];
for (var i = 0; i < eventsLength; i++) {
// not sure what is next
if ((events[i].start <= SlotID) && (SlotID < events[i].end)) {
CurrEvent.push(events[i]);
}
}
var EventTable = $('<table style="border:1px dashed purple;width:100%"><tr></tr></table');
for (var x = 0; x < CurrEvent.length; x++) {
var newEvt = $('<td></td>');
newEvt.html(CurrEvent[x].start+"-"+CurrEvent[x].end);
newEvt.addClass("timeEvent");
newEvt.css("width", (100/CurrEvent.length)+"%");
newEvt.css("height", EventHeight);
newEvt.prop("id", CurrEvent[x].id);
newEvt.appendTo(EventTable.find("tr"));
}
EventTable.appendTo(CurSlot);
});
};
return {
layOutDay: layOutDay
}
}();
var events = [
{
id: 1,
start: 30,
end: 150},
{
id: 2,
start: 180,
end: 240},
{
id: 3,
start: 180,
end: 240}];
$(document).ready(function() {
var SlotId = 0;
$(".slot").each(function(index, val) {
var newDiv = $('<div></div>');
newDiv.prop("SlotID", SlotId)
//newDiv.html(SlotId);
newDiv.height($(this).height()+2);
newDiv.addClass("timeSlot");
newDiv.appendTo($("#calander"));
SlotId = SlotId + 30;
});
// call now
Calendar.layOutDay(events);
});
I strongly recommend to use http://arshaw.com/fullcalendar/
demo: http://jsfiddle.net/jGG34/2/
whatever you are trying to achieve is already implemented in this, just enable the day mode and do some css hacks.. thats it!!
If I understand you correctly, the input is a list of events with start and end times, and the output is, for each event, the column number of that event and the total number of columns during that event. You basically need to color an interval graph; here's some pseudocode.
For each event e, make two "instants" (start, e) and (end, e) pointing back to e.
Sort these instants by time, with end instants appearing before simultaneous start instants.
Initialize an empty list component, an empty list column_stack, a number num_columns = 0, and a number num_active = 0. component contains all of the events that will be assigned the same number of columns. column_stack remembers which columns are free.
Scan the instants in order. If it's a start instant for an event e, then we need to assign e a column. Get this column by popping column_stack if it's nonempty; otherwise, assign a new column (number num_columns) and increment num_columns (other order for 1-based indexing instead of 0-based). Append e to component. Increment num_active. If it's an end instant, then push e's assigned column onto column_stack. Decrement num_active. If num_active is now 0, then we begin a new connected component by popping all events from component and setting their total number of columns to num_columns, followed by clearing column_stack and resetting num_columns to 0.
I would approach the problem as follows.
A divider is any moment within a day that no event crosses. So if you have one event from 9 am to 11 am and another from 11 am to 1 pm, and no other events, there is a divider at 11 am, and at any time at 1 pm or later, and at any time at 9 am or earlier.
I would divide every day into a set of "eventful time spans" that are maximum time spans containing no dividers. For every eventful time span, I would calculate the maximum number of concurrently overlapping events, and use that as the "column number" for that event span. I would then layout every eventful time span greedily on the calculated number of columns so that every event would be laid out as left as possible, in the order of the starting times of the events.
So, for example the following schedule:
A 9 am - 11 am
B 10 am - 12 pm
C 10 am - 1 pm
D 1 pm - 2 pm
E 2 pm - 5 pm
F 3 pm - 4 pm
would be processed as follows. The eventful time spans are 9 am - 1 pm, 1 pm - 2 pm, and 2 pm - 5 pm as there are dividers at 1 pm and 2 pm (no event crosses those times).
On the first span, there are maximum three overlapping events, on the second only one, and on the third, two.
The columns are allocated like this:
9 am - 10 am | | | |
10 am - 11 am | | | |
11 am - 12 pm | | | |
12 pm - 1 pm | | | |___ end of first e.t.s.
1 pm - 2 pm | |___ end of second e.t.s.
2 pm - 3 pm | | |
3 pm - 4 pm | | |
4 pm - 5 pm | | |
After which the events are filled in, in their chronological order greedily:
9 am - 10 am | A |###|###|
10 am - 11 am |_A_| B | C |
11 am - 12 pm |###|_B_| C |
12 pm - 1 pm |###|###|_C_|
1 pm - 2 pm |_____D_____|
2 pm - 3 pm | E |#####|
3 pm - 4 pm | E |__F__|
4 pm - 5 pm |__E__|#####|
which looks very reasonable. # denotes free space
The keypoint is to count all the Collision from your appointments. There is a pretty simple algorithm to do so:
sort the appointment array appointments according to start-date and breaking ties with end-date.
maintain an array active with all active appointments, which is empty in the beginning
maintain a value collision for each appointment (since you already have objects, you could store it as another property) [{id : 1, start : 30, end : 150, collisions : 0},...]
iterate through appointments with the following steps:
shift the first item (i) from appointments
compare start-date from i with enddates from all items j in active - remove all items where j.enddate < i.startdate
update collisions from all remaining j (+1 for each)
update collision of i ( i.collision = active.length)
pop i into array active
repeat these steps for all items of appointments.
Example:
(beware, pseudo-code) :
var unsorted = [7,9],[2,8],[1,3],[2,5],[10,12]
// var appointments = sort(unsorted);
var appointments = [1,3],[2,5],[2,8],[7,9],[10,12]
// now for all items of appoitments:
for (var x = 0; x<appointments.length;x++){
var i = appointments[x]; // step 1
for (var j=0; j<active.length;j++){
// remove j if j.enddate < j.startdate // step 2
// else j.collision += 1; // step 3
}
i.collision = active.length; // step 4
active.pop(i); // step 5
}
If you collect the items removed from active, you get an array, sorted by end-dates before startdates, which you can use for displaying the divs.
Now, try out if you can get the code to make it work and write a comment if you need further help.

Categories

Resources