best way to loop through and combine products - javascript

I'm looking for your personal thoughts on the best way to do this as there a number of ways that I can see to get to the same end.
I have a store front that has multiple different booklets that can be ordered in any combination.
Each booklet item has L x W x H and weight in the database. They're all really the same L x W, it's the thickness and weight that varies.
I also have a set max box size.
I know I can fit a height of 10.5 in the largest box size.
I can combine the books however for shipping. So I want to fill the box to the max I can, then repeat until I only have a partial box.
The final result I need is an array of each of the boxes and their weight.
Example:
Items ordered
100 of Book 1 with a thickness of .05 and weight of .15
200 of Book 2 with a thickness of .07 and a weight of .23
Heights
100 * .05 = 5
200 * .07 = 14
Weights
100 * .15 = 15
200 * .23 = 46
Box 1 Contains
150 of Book 2
Height: 150 * .07 = 10.5
Weight: 150 * .23 = 34.5
Box 2 Contains
50 of Book 2 and 100 of Book 1
Height: (100 * .05) + (50 * .07) = 8.5
Weight: (100 * .15) + (50 * .25) = 27.5
How would you go about looping through the information to get the output?
Start with one product, figure the height, divide it out to full boxes, calculate the weight of those boxes and add each full box to the array, take the height of the partial box and move on to the next product? Or......??
Static coding would be "easy", but I'm trying to code this dynamically so that I don't have to come back in every time I add a new product.
-edit-
Here is my current loop, but this is only totaling the quantity and dividing into boxes. The current products are all the same size / shape, so this worked fine then.
if($zip_code != '')
{
my $item_list = enc_sql_select_multi("SELECT * FROM `$PREF{shopping_carts_table}` WHERE `cart_id` = '$cart_id' AND `sold` = '0' AND `deleted_from_cart` = '0'");
my $item_qty = '0';
foreach my $k (sort { $a <=> $b } keys %$item_list)
{
$item_qty =~ s/,//g;
my $item_id = $$item_list{$k}{id};
my $item_qty_new = $$item_list{$k}{option1value};
$item_qty_new =~ s/,//g;
$item_qty = $item_qty + $item_qty_new;
}
if ($item_qty != '0')
{
$item_qty =~ s/,//g;
if ( $item_qty == 25 )
{
my $tempCount = $carton_specs{25}{boxNo};
$tempCount++;
$carton_specs{25}{boxNo} = $tempCount;
}
elsif ( $item_qty == 50 )
{
my $tempCount = $carton_specs{50}{boxNo};
$tempCount++;
$carton_specs{50}{boxNo} = $tempCount;
}
elsif ( $item_qty == 100 )
{
my $tempCount = $carton_specs{100}{boxNo};
$tempCount++;
$carton_specs{100}{boxNo} = $tempCount;
}
elsif ($item_qty > 100 && $item_qty < 5000)
{
my $fullBoxCt = int($item_qty / 200);
my $tempCountFull = $carton_specs{200}{boxNo};
$tempCountFull = $tempCountFull + $fullBoxCt;
$carton_specs{200}{boxNo} = $tempCountFull;
my $partBoxQty = $item_qty - (200 * $fullBoxCt);
if ($partBoxQty != 0)
{
my $tempCountPart = $carton_specs{$partBoxQty}{boxNo};
$tempCountPart++;
$carton_specs{$partBoxQty}{boxNo} = $tempCountPart;
}
}
else
{
#shipDetails =
(
{
Code => 1000,
Price => '0.00'
},
{
Code => 1500,
Price => '0.00'
}
);
return (#shipDetails);
}
}

There is no getting away from this being a classic example of the bin-packing problem and while I am no mathematician either, a lot of people who are have given this considerable thought. So:
There is no known optimal solution to this problem that can be computed in polynomial time and it would appear the size of your inputs can be quite large. Therefore, you should adopt one of the following heuristics:
First Fit Algorithm: Process the booklets one by one placing it in the first box that has enough capacity to hold the booklet. If there are no such boxes, a new box must be started and added to the list of boxes available.
Best Fit Algorithm: Process the booklets one by one placing it in the box where it fits the tightest (has the smallest capacity left). If there are no boxes that can hold the booklet, a new box must be started and added to the list of boxes available.
First Fit Decreasing Algorithm: First order the booklets by decreasing height and then perform the First Fit Algorithm. In your example you would be packing up the "Book 2" booklets first.
Best Fit Decreasing Algorithm: First order the booklets by decreasing height and then perform the Best Fit Algorithm.
But, of course, your orders consist of N items of of a certain type of booklet. We will take the prescription "one at a time" in the above algorithms loosely. So if you are doing, for example, the First Fit Algorithm, then when you find the first box that has the capacity to hold the booklet, it is easy enough to calculate the maximum number of those booklets, M, that box could hold and process up to min(M, N_LEFT) booklets at one time where N_LEFT is the number of booklets of that size you still have left to pack up. Likewise for any of the other three algorithms.
You can survey other bin-packing heuristics of which the above are just a sample four. I can't say one is necessarily superior to the others in all cases. I think any reasonable implementation of any of them will be satisfactory for your purposes. Anyway, this is my take.

This is a bin-packing problem. The best optimal algorithm is Korf's. If this is too slow for the amount of data you have, there are various algorithms that provide good approximate answers faster.

Related

How to determine possible combinations of items based on item resources?

I'm a current software development student looking to get some help with my latest self-learning project.
My coworkers and I play Settlers of Catan, so a couple of weeks ago I thought it would be a neat idea if I could make a site that would tell a person what they can buy with the resource cards they have in their hand.
For those who don't know, the basic Catan purchases are as follows:
Building
Cost
City
2 wheat, 3 ore
Settlement
1 wood, 1 brick, 1 wool, 1 ore
Road
1 wood, 1 brick
Dev. Card
1 wool, 1 wheat, 1 ore
What I need to do is take a players hand, which has any number of resource cards, and run it through a function that will determine a list of possible options along with the cards left over.
As an example it should return something like:
Option 1: You can build 1 City, 1 Road. 1 Sheep left over.
Option 2: You can build 1 Settlement. 3 Ore and 1 Wheat left over.
The function that I am currently using in JavaScript is shown below:
/* Below dictionary is updated via HTML inputs
Also sheep == wool, ignore the lack of using the accurate card name
*/
var resources = { 'wood': 1, 'brick': 1, 'sheep': 1, 'wheat': 2, 'ore': 3 };
/* Takes resources (your hand), then tries to determine what
the player can build with that hand */
function basicBuilder(resources) {
var buildDict = { 'roads': 0, 'settlements': 0, 'cities': 0, 'dcards': 0 }
while (resources['wheat'] >= 2 && resources['ore'] >= 3) {
resources['wheat'] -= 2;
resources['ore'] -= 3;
buildDict['cities'] += 1;
}
while (resources['wood'] >= 1 && resources['brick'] >= 1 && resources['sheep'] >= 1 && resources['wheat'] >= 1) {
resources['wood'] -= 1;
resources['brick'] -= 1;
resources['sheep'] -= 1;
resources['wheat'] -= 1;
buildDict['settlements'] += 1;
}
while (resources['sheep'] >= 1 && resources['wheat'] >= 1 && resources['ore'] >= 1) {
resources['sheep'] -= 1;
resources['wheat'] -= 1;
resources['ore'] -= 1;
buildDict['dcards'] += 1;
}
while (resources['wood'] >= 1 && resources['brick'] >= 1) {
resources['wood'] -= 1;
resources['brick'] -= 1;
buildDict['roads'] += 1;
}
return buildDict;
}
This way would work fine if none of the buildings shared resources, but since some buildings use the same cards it doesn't suit my purpose. For example if you use the resources that I supplied, buildDict['settlements'] will equal 0 since all the wheat was already used up by the city.
There has to be a better and cleaner way to go about this, so my question to all of you is this:
How can I best determine the players possible build choices on their turn?
As a reminder, the input will be a hand of resources in dictionary form.
The output should be similar to:
Option {n}: You can build {x} Cities, {x} Settlements, {x} Roads, etc... with {x} cards leftover
Bonus points for any of the following:
Solutions that easily allow for additional buildings later on (expansion packs)
Solutions that can somehow accept resource trading (in Catan, 4 of a resource can be traded in for 1 of any other resource. If you own a port, this can be reduced to either 3 or 2 for one trades)
Solutions that will still allow for the display of what the leftover cards are
EDIT - Reworded the question to be more specific per bots request, very new to stackoverflow. See past question above
With a hand (dictionary) full of resource cards, what is a good JavaScript way to find all the possible combinations of buildings that could be purchased?
Each building could be purchased multiple times
There is no maximum or fixed hand size
When a resource is used for a building, it is removed from the hand

javascript get random number: lower probability to get higher number in the interval

Ok, so I have very big array of numbers in javascript:
[1, 1.01, 1.02, 1.03, ..., 1.99, 2, ..., 9.98, 9.99, ..., 299.99, 300]
And what I need is to get one of them using random segment. So basically I need random number but the catch is that I need to get random using the lottery style. So the chance to get "1" will be 30 000 (very hight) and the chance to get 1.01 will be 29 999. But the chance to get 300 will be very low according of all numbers in this array.
I hope you will understand the problem and will help me to solve this. As I have mentioned before, this have to be made 100% randomly and I have no idea how to make it..
The solution I had so far:
I was trying to expanse the array by adding multiple same numbers and lower the count of it by each step. So I have added 30000 units of 1 and 29999 units of 1.01 ... and 2 units of 299.99 and one unit of 300. But the array got very large and I came here to find better solution.
Also I have found this: https://stackoverflow.com/a/13758064/5786106
and it seems to be the answer to me but I don't know how to use it with the decimal system (0.01, 0.02, ... 0.99)
var num = Math.pow(Math.floor(Math.random()*10), 2);
One solution would be to make the very large array you propose, but to make it imaginary, without ever constructing that object in code.
How long will the imaginary array be? Well your array has (300 - 1) * 100 + 1 = 29,901 elements in it. Then there are (29,901 + 1) * (29,901 / 2) = 447,049,851 elements in the imaginary array. So the first step is to generate a random integer between 0 and 447,049,850:
var imaginaryIndex = Math.floor(Math.random() * 447049851);
The next step is to determine which real index in your original array corresponds to the imaginaryIndex in the imaginary array.
var indexFromEnd = 0;
while((indexFromEnd + 2) * ((indexFromEnd + 1) / 2) < imaginaryIndex)
indexFromEnd++;
Finally, you need to calculate the value of the element in your array based on where it is in your array:
return 300 - (indexFromEnd * 0.01);
Now let's clean that up and put it in a nice, reusable function:
function triangularWeightedRandomSelect(myArray){
var imaginaryIndex =
Math.floor(Math.random() * (myArray.length + 1) * myArray.length / 2);
var indexFromEnd = 0;
while((indexFromEnd + 2) * ((indexFromEnd + 1) / 2) < imaginaryIndex)
indexFromEnd++;
return myArray[myArray.length - 1 - indexFromEnd];
}

Creating a Profit Calculator

I'm trying to create a basic profit calculator but I am struggling with one issue.
I've written some basic javascript and the formula almost works. However my issue is that the decimal point doesn't seem to want to work properly. For example:
What is the case cost: 2.80
How may units per case: 2
What is the sell price: 3.15
Total Profit = 1.75 Profit should of course be, 0.175
I'm a complete newbie to JavaScript so your help would be much appreciated.
<form id="profitCalculator">
<p><label>What is the case cost? <input type="text" name="casecost"></label></p>
<p><label>How many packs / units per case? <input type="text" name="packs"></label></p>
<p><label>What is the sell price? <input type="text" name="sell_price"></label></p>
<p>Total profit £: <input type="text" name="profit"></p>
document.getElementById('profitCalculator').onclick = function () {
var casecost = this.elements['casecost'].value || 0;
var packs = this.elements['packs'].value || 0;
var sell_price = this.elements['sell_price'].value || 0;
var profit = sell_price - casecost / packs;
this.elements['profit'].value = profit.toFixed(2); }
Thanks
It should be
var profit = (sell_price - casecost) / packs;
BUT - Never calculate currency with decimals in Javascript!
Javascript will truncate decimal values when they become to long, possibly resulting in nasty rounding errors. Always multiply your values by 100, then calculate everything, and at last, divide by 100 again.
Look at order of operations, you may know this as 'BODMAS'
Supporting Link: http://www.mathsisfun.com/operation-order-bodmas.html
Change to (sell_price - casecost) / packs;
your problem occurs because operators procedence.
var profit = sell_price - casecost / packs;
/ (division) occurs first than - (minus).
As your example.
2.80 / 2 = 1.4
3.15 - 1.4 = 1.75
You should put some parenthesis covering what has to priority, in your case, to get the value 0.175, you should put the like this.
(3.15 - 2.80) / 2 = 0.175
in code
var profit = (sell_price - casecost) / packs;
See MDN's reference on Operator Precedence and you'll see that division (and multiplication) is done before addition or subtraction. So you have essentially:
3.15 - (2.80 / 2) = 1.75
Instead of:
(3.15 - 2.80) / 2 = 0.175
Also note, as #Adrian Schmidt pointed out, using floating point numbers for math is a bad idea. If you do that above calculation in javascript you actually get:
0.17500000000000004
Because computers don't have infinite precision when representing floating point numbers. See, for example: Is floating point math broken?
So your formula should be:
(sell_price - casecost) / packs
Another thing to consider is that the values you get from your text boxes are strings, not numbers. Your formula works because there is no - operator for strings, so javascript automatically converts your values to numbers. But this is a dangerous thing to rely on. For example, if you did this:
sell_price + casecost
With your example inputs, the result would be:
"3.152.80"
Because it's doing string concatenation, not addition.
So it's worth using parseFloat to convert your strings. (and parseInt for packs as it is, presumably, an integer)
So a complete example might look like this:
var casecost = parseFloat(this.elements['casecost'].value) * 100 || 0;
var packs = parseInt(this.elements['packs'].value, 10) || 0;
var sell_price = parseFloat(this.elements['sell_price'].value) * 100 || 0;
var profit = ((sell_price - casecost) / packs) / 100;
this.elements['profit'].value = profit.toFixed(2);
Also note that if packs is 0, then you'll have a divide by zero error. You'll want to add logic to check the value of packs and do something when it's zero (not calculate the profit).

when generating normally-distributed random values, what is the most efficient way to define the range?

FYI: random == pseudo-random
A. when generating uniformly-random numbers, I can specify a range, i.e.:
(Math.random()-Math.random())*10+5
//generates numbers between -5 and 15
B. generating a set of random values with a version of Gaussian-esque normal randomness:
//pass in the mean and standard deviation
function randomNorm(mean, stdev) {
return Math.round((Math.random()*2-1)+(Math.random()*2-1)+(Math.random()*2-1))*stdev+mean);
}
//using the following values:
{
mean:400,
standard_deviation:1
//results in a range of 397-403, or +-range of 3
},
{
mean:400,
standard_deviation:10
//results in a range of 372-429, or +-range of 30
},
{
mean:400,
standard_deviation:25
//results in a range of 326-471, or +-range of 75
}
each one gives me a range of approximately standard_deviation*(+-3) (assuming I left the program running longer).
C. I can calculate this range as follows:
assuming I want a range from 300-500, so var total_range = 200;
my mean is 400, my +-range is total_range/2 (var r = 100)
so standard_deviation would be r/3 or in this case 33.333.
This seems to be working, but I have no idea what I'm doing with math so I feel like an idiot, this solution feels kludgy and not totally accurate.
My question:
is there some formula that I'm dancing around that can help me here? my requirements are as follows:
must be able to define a range of numbers accurately.
must be done in JavaScript, as efficiently as possible.
I think maybe I'm close but it's not quite there.
Subtracting two random numbers doesn't give you a normal distribution, it will give you numbers that decline linearly on both sides of zero. See the red diagram in this fiddle:
http://jsfiddle.net/Guffa/tvt5K/
To get a good approximation of normal distribution, add six random numbers together. See the green diagram in the fiddle.
So, to get normally distributed random numbers, use:
((Math.random() + Math.random() + Math.random() + Math.random() + Math.random() + Math.random()) - 3) / 3
This method is based on the central limit theorem, outlined as the second method here: http://en.wikipedia.org/wiki/Normal_distribution#Generating_values_from_normal_distribution
I wanted to have gaussian random numbers between 0 and 1, and after many tests (thanks to #Guffa answer too) I found this to be the best:
function gaussianRand() {
var rand = 0;
for (var i = 0; i < 6; i += 1) {
rand += Math.random();
}
return rand / 6;
}
And as a bonus:
function gaussianRandom(start, end) {
return Math.floor(start + gaussianRand() * (end - start + 1));
}

Gaps in Data on Google Charts Continuous Axis

I have a Google Chart with a continuous date-time X axis. My data comes in short bursts, with long delays between the bursts. I'd like to make the Chart have a non-continuous X axis, but still have the auto-generated timestamps during the samples. Is that possible?
Basically, say I have 3 samples, each which have 300 datapoints, recorded across 10 second intervals, but with hour gaps between them. I'd like to have my chart show the 30 seconds of data at a zoom level where it can be distinguished. Am I stuck?
Edit: Per #jmac's suggestion, here is an example of what the data looks like:
1360096658270, 10.228335
1360096658274, 10.308437
1360096658277, 10.294770
[...]
1360096673968, 9.014943
1360096673969, 8.971434
1360096673970, 9.041739
1360096673971, 9.097484
^^-- 15 seconds
<--- (~10 days)
1360989176509, 9.856928
1360989176513, 9.852907
1360989176517, 9.861740
1360989176523, 9.820416
1360989176527, 9.871401
Method 1: Multiple Charts
This is probably the simplest in concept (though still a hassle).
Summary:
Split data in to groups (eliminate the gaps)
Create a separate chart for each group
Eliminate the vAxis labels for every chart past the first
Create a consistent vAxis min/max value
Use CSS to line the charts up side to side
Details:
If you have a static data set, you can just split it by hand. If it isn't static, then you have to write some javascript to split up your data. I can't really help you here since I don't know how your data works.
As far as setting up the charts, I'll leave that up to you. I don't know how you want them formatted, so again I can't really help you with the current info.
To create a consistent axis value for all charts, you need to use some basic math in a javascript function to assign the same numbers to each vAxis max/min value. Here is a sample:
// Take the Max/Min of all data values in all graphs
var totalMax = 345;
var totalMin = -123;
// Figure out the largest number (positive or negative)
var biggestNumber = Math.max(Math.abs(totalMax),Math.abs(totalMin));
// Round to an exponent of 10 appropriate for the biggest number
var roundingExp = Math.floor(Math.log(biggestNumber) / Math.LN10);
var roundingDec = Math.pow(10,roundingExp);
// Round your max and min to the nearest exponent of 10
var newMax = Math.ceil(totalMax/roundingDec)*roundingDec;
var newMin = Math.floor(totalMin/roundingDec)*roundingDec;
// Determine the range of your values
var range = newMax - newMin;
// Define the number of gridlines (default 5)
var gridlines = 5;
// Determine an appropriate gap between gridlines
var interval = range / (gridlines - 1);
// Round that interval up to the exponent of 10
var newInterval = Math.ceil(interval/roundingDec)*roundingDec;
// Re-round your max and min to the new interval
var finalMax = Math.ceil(totalMax/newInterval)*newInterval;
var finalMin = Math.floor(totalMin/newInterval)*newInterval;
Method 2: Multiple Series
As long as the people viewing your data understand they are different sets, then there's no reason the axis needs to say the exact date/time as long as they can easily figure that out elsewhere.
Summary:
Separate your data in to different series for each 'sequence'
Artificially shorten the gaps between sequences (if they are 15 seconds each, then have a 5 second gap between series, or just start every 15 seconds)
Format each different series with a name labeling when the run started/ended
Details:
Again, you will have to split your data manually or create javascript to do it, but what you want to do is to move each set of numbers in to its own column, like so:
1360096658270, 10.228335, null
1360096658274, 10.308437, null
1360096658277, 10.294770, null
[...]
1360096673968, 9.014943, null
1360096673969, 8.971434, null
1360096673970, 9.041739, null
1360096673971, 9.097484, null
^^-- 15 seconds
<--- (~10 days)
1360989176509, null, 9.856928
1360989176513, null, 9.852907
1360989176517, null, 9.861740
1360989176523, null, 9.820416
1360989176527, null, 9.871401
This will make each series be a different color (and have a different label in the legend/on mouseover), so you can see the difference between runs, but also get a nice tooltip saying "This data was gathered from X to Y" so that if the time the data was taken is important, it's still in there (albeit not on the X axis).
These are the easiest ways.
Method 3: Manually Editing the X-Axis Labels
The third way is the most flexible but also takes the most work. You can create a custom javascript function to manipulate the X-axis labels in SVG. More details on this here by #jeffery_the_wind:
/*
*
* The following 2 functions are a little hacky, they have to be done after calling the "draw" function
* The bubble chart originally displays only numbers along the x and y axes instead of customer or product names
* These 2 functions replace those numbers with the words for the customers and products
*
*/
for ( var i = -2; i < products.length + 1; i ++ ){
$('#customer_product_grid svg text[text-anchor="start"]:contains("'+i+'")').text(function(j,t){
if (t == i){
if (i >= products.length || i < 0){
return " ";
}
return products[i];
}
});
}
for ( var i = -2; i <= customers.length + 3; i ++ ){
$('#customer_product_grid svg text[text-anchor="end"]:contains("'+i+'")').text(function(j,t){
if (i >= customers.length + 1 || i <= 0){
return " ";
}else if (t == i){
return customers[i-1];
}
});
}
Google's documentation on customizing axes describes how to do what you're asking. You can change the type of your column to a string and populate with formatted Date strings.

Categories