Algorithm to price bulk discounts - algorithm

i am designing a Chinese auction website.
Tickets ($5, $10 & $20) are sold either individually, or via packages to receive discounts.
There are various Ticket packages for example:
5-$5 tickets = receive 10% off
5-$10 tickets = receive 10% off
5-$20 tickets = receive 10% off
5-$5 tickets + 5-$10 tickets + 5-$20 tickets = receive 15% off
When users add tickets to their cart, i need to figure out the cheapest package(s) to give them. the trick is that if a user adds 4-$5 tickets + 5-$10 tickets + 5-$20 tickets, it should still give him package #4 since that would be the cheapest for him.
Any help in figuring out a algorithm to solve this, or any tips would be greatly appreciate it.
thanks
EDIT
i figured out the answer, thanks all, but the code is long.
i will post the answer code if anyone still is interested.

After selling the customer as many complete packages as possible, we are left with some residual N of tickets desired of each of the 3 types ($5, $10, $20). In the example you gave, the quantities desired range from 0 to 5 (6 possible values). Thus, there are only 214 possible residual combinations (6 ** 3 - 2; minus 2 because the combinations 0-0-0 and 5-5-5 are degenerate). Just pre-compute the price of each combination as though it were purchased without package 4; compare that calcuation to the cost of package 4 ($148.75); this will tell you the cheapest approach for every combination.
Is the actual number of packages so large that a complete pre-computation wouldn't be a viable approach?

One approach is dynamic programming.
The idea is that if the buyer wants x of item A, y of item B, and z of item C, then you should compute for all triples (x', y', z') with 0 <= x' <= x and 0 <= y' <= y and 0 <= z' <= z the cheapest way to obtain at least x' of A, y' of B, and z' of C. Pseudocode:
for x' = 0 to x
for y' = 0 to y
for z' = 0 to z
cheapest[(x', y', z')] = min over all packages p of (price(p) + cheapest[residual demand after buying p])
next_package[(x', y', z')] = the best package p
Then you can work backward from (x, y, z) adding to the cart the packages indicated by next_package.
If there are many different kinds of items or there are many of each item, branch and bound may be a better choice.

First, calculate how many full Package 4s you need. Get them out of the way.
full_package_4_count = min(x, y, z) mod 5.
x = x - 5 * full_package_4_count
y = y - 5 * full_package_4_count
z = z - 5 * full_package_4_count
Now, there may still be worth buying some more Package 4s, even though they didn't actually want to buy that many tickets.
How many of them could there be?
partial_package_4_max = (max(x, y, z) + 4) mod 5
Now loop to try each of these out:
best_price = 10000000
for partial_package_4_count = 0 to partial_package_4_max:
-- Calculate how much we have already spent.
price = (full_package_4_count + partial_package_4_count) * 175 * (1-0.15)
-- Work out how many additional tickets we want.
x' = max(0, x - 5 * partial_package_count)
y' = max(0, y - 5 * partial_package_count)
z' = max(0, z - 5 * partial_package_count)
--- Add cost for additional tickets (with a 10% discount for every pack of 5)
price = price + x' mod 5 * 25 * (1-0.10) + x' div 5 * 5
price = price + y' mod 5 * 50 * (1-0.10) + x' div 5 * 10
price = price + y' mod 5 * 100 * (1-0.10) + x' div 5 * 20
if price < best_price
best_price = price
-- Should record other details about the current deal here too.

Related

Designing an algorithm for retirement planning, searching for retirement rate %

My client is a financial advisor and helps people create retirement plans. His current process is to take all their financial data, type them into a spreadsheet and, based on the retiree's goals, discover the rate at which they can withdraw from their savings/assets/investments (as a percentage). That percentage is the solution to a problem, and the way he finds it now is to guess at it (e.g. "Let's try 5% (too high). OK, how about 1% (too low). Hmm, 2.5%? (too high) ... until he finds the percentage that satisfies the conditions of the retiree).
If I were to program this exactly how he does it, then I think it's just a binary search algorithm. But it feels like there's a more clever way to do this. He's basically using the compound interest formula, A=P(1+r/n)^nt, to discover 'r' in that equation, but it has to be done over a period of several decades, and each year requires about a dozen calculations in the backend. So roughly a dozen, times maybe 30 years equals ~300 calculations for one iteration of the binary search.
Sorry if this isn't detailed enough, but to be more specific requires and exhaustive level of detail.
Has anyone, perhaps someone in the financial sector, dealt with this kind of search?
Sorry if I've misunderstood what you're asking. If it's just to find r in the formula you give:
A = P (1 + r / n) ^ nt
A/P = (1 + r / n) ^ nt
log_nt A/P = 1 + r / n
log_nt A/P - 1 = r / n
n (log_nt A/P - 1) = r
More generally, if you think you might be able to get a closed-form solution, you should try really hard to write down your model and equations in such a way that you can find such a solution.
Some benefits to this approach:
The result is easier to implement and to understand. You can put the derivation of the formula in comments if you like.
The result is virtually guaranteed to be more precise than what you'd get from a search technique.
It will run fast.
Here's how I might model the problem:
P: principal
r: monthly interest gained on principal
w: amount withdrawn from principal
T: number of months over which the principal is to be withdrawn
B(t): balance at time 0 <= t <= T
B(0) = P
B(T) = 0
We want to find w. We write our recurrences:
B(0) = P
B(t+1) = B(t) * r - w
We can write out a few terms:
B(0) = P
B(1) = P * r - w
B(2) = (P * r - w) * r - w = P * r^2 - wr - w
B(3) = (P * r^2 - wr - w) * r - w = P * r^3 - wr^2 - wr - w
...
B(t) = P * r^t - w(r^(t-1) + r^(t-2) + ... + 1)
= P * r^t - w(r^t - 1)/(r - 1)
Now we set B(T) = 0 assuming we want the money to run out, then solve for w:
0 = B(T) = P * r^T - w(r^T - 1)/(r - 1)
w(r^T - 1)/(r - 1) = P * r^T
w = P * r^T * (r - 1) / (r^T - 1)
Suppose that P = $1,000,000, r = 1.0025 (just above 3% annually), and T = 360 (retirement savings to last 30 years). Then we have
w = $1,000,000 * 1.0025^360 * (1.0025 - 1) / (1.0025 ^ 360 - 1)
= $4,216
If you want to model the situation differently, you need only write it down and follow the same steps as this. With any luck, your model will have some closed form solution, as the two problems I've solved in this answer did.

Calculate cash flows given a target IRR

I apologize if the answer for this is somewhere already, I've been searching for a couple of hours now and I can't find what I'm looking for.
I'm building a simple financial calculator to calculate the cash flows given the target IRR. For example:
I have an asset worth $18,000,000 (which depreciates at $1,000,000/year)
I have a target IRR of 10% after 5 years
This means that the initial investment is $18,000,000, and in year 5, I will sell this asset for $13,000,000
To reach my target IRR of 10%, the annual cash flows have to be $2,618,875. Right now, I calculate this by hand in an Excel sheet through guess-and-check.
There's other variables and functionality, but they're not important for what I'm trying to do here. I've found plenty of libraries and functions that can calculate the IRR for a given number of cash flows, but nothing comes up when I try to get the cash flow for a given IRR.
At this point, I think the only solution is to basically run a loop to plug in the values, check to see if the IRR is higher or lower than the target IRR, and keep calculating the IRR until I get the cash flow that I want.
Is this the best way to approach this particular problem? Or is there a better way to tackle it that I'm missing? Help greatly appreciated!
Also, as an FYI, I'm building this in Ruby on Rails.
EDIT:
IRR Function:
NPV = -(I) + CF[1]/(1 + R)^1 + CF[2]/(1 + R)^2 + ... + CF[n]/(1 + R)^n
NPV = the Net Present Value (this value needs to be as close to 0 as possible)
I = Initial investment (in this example, $18,000,000)
CF = Cash Flow (this is the value I'm trying to calculate - it would end up being $2,618,875 if I calculated it by hand. In my financial calculator, all of the cash flows would be the same since I'm solving for them.)
R = Target rate of return (10%)
n = the year (so this example would end at 5)
I'm trying to calculate the Cash Flows to within a .005% margin of error, since the numbers we're working with are in the hundreds of millions.
Let
v0 = initial value
vn = value after n periods
n = number of periods
r = annual rate of return
y = required annual net income
The one period discount factor is:
j = 1/(1+r)
The present value of the investment is:
pv = - v0 + j*y + j^2*y + j^3*y +..+ j^n*y + j^n*vn
= - v0 + y*(j + j^2 + j^3 +..+ j^n) + j^n*vn
= - v0 + y*sn + j^n*vn
where
sn = j + j^2 + j^3 + j^4 +..+ j^n
We can calulate sn as follows:
sn = j + j^2 + j^3 + j^4 +..+ j^n
j*sn = j^2 + j^3 + j^4 +..+ j^n + j^(n+1)
sn -j*sn = j*(1 - j^n)
sn = j*(1 - j^n)/(1-j)
= (1 - j^n)/[(1+r)(r/(1+r)]
= (1 - j^n)/r
Set pv = 0 and solve for y:
y*sn = v0 - vn * j^n
y = (v0 - vn * j^n)/sn
= r * (v0 - vn * j^n)/(1 - j^n)
Our Ruby method:
def ann_ret(v0, vn, n, r)
j = 1/(1+r)
(r * (v0 - vn * j**n)/(1 - j**n)).round(2)
end
With annual compounding:
ann_ret(18000000, 13000000, 5, 0.1) # => 2618987.4
With semi-annual compounding:
2 * ann_ret(18000000, 13000000, 10, 0.05) # => 2595045.75
With daily compounding:
365 * ann_ret(18000000, 13000000, 5*365, 0.10/365) # => 2570881.20
These values differ slightly from the required annual return you calculate. You should be able to explain the difference by comparing present value formulae.
There's a module called Newton in Ruby... it uses the Newton Raphson method.
I've been using this module to implement the IRR function into this library:
https://github.com/Noverde/exonio
If you need the IRR, you can use like this:
Exonio.irr([-100, 39, 59, 55, 20]) # ==> 0.28095

Suggest Ranking algorithm for Multi User Sortable Lists

I'm building a site where I am giving users the ability to drag and drop to order a list of items to rank them for their "personal view." They can optionally remove an item to hide it from their "personal view."
My question is how can I fairly implement a ranking algorithm to determine the ordering of the items for a shared view that doesn't penalize new items.
It would also help if that can also be used to rank where new items would show up in a users personal list.
So if a new item comes along that is highly ranked by other users, we could display it where we predict the user would rank it related to their other rankings.
My initial thoughts is give points to each item ranked by a user = to the position in a users ranked list. (ex. If there are 10 items, give rank 1 10 pts, 2 9 pts, etc, with negative points awarded for items hidden by the user). And the shared view would sort based on total points. But this would not work well for new items that were largely unranked, and would not easily move up the ladder.
So any thoughts on a fair algorithm that can be predictive for new items?
So I think I have a working solution. By combining the approach I mentioned in the question comment, with the lower bound of Wilson's score confidence interval for a Bernoulli parameter the score seems to align to my expectations.
So to rehash the approach from my comment: user item score = count of items + 1 - rank in the list / count of items. (1 of 3 = 1, 2 of 3 = .667, 2 of 5 = .8).
to give an overall item score I plug into the Wilson formula:
(phat + z*z/(2*n) - z * Math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)
Where phat = average of scores, n is number of rankings, z=1.96 (for a 95% confidence ranking).
I mocked up some data in Excel and played around with different scenarios and liked the results. Will move to implementation. Thanks for the help
How about implementing something similar to 9gag ranking system. You can have a shared page where highest ranking items show up and a voting page where users can see new items and rank them accordingly.
I think the important point here is to look at what other users ranking are with respect to other items as well.
"This item is often ranked 3rd" is not useful, I think, whereas "Item under consideration (which we shall call A) is ranked better than item B most of the time" is, because it allows you to create a (maybe fuzzy) ordering of your list of items under consideration.
Essentially, for a new item in a users list, you would implement a kind of insertion sort, where the comparison of two elements is determined by their average order within other peoples lists. In fact, any sort algorithm would work, as long as it depends on having an order between two given elements.
here is my Wilson's score confidence interval for a Bernoulli parameter in node.js
wilson.normaldist = function(qn) {
var b = [1.570796288, 0.03706987906, -0.0008364353589, -0.0002250947176, 0.000006841218299, 0.000005824238515, -0.00000104527497, 0.00000008360937017, -0.000000003231081277, 0.00000000003657763036, 0.0000000000006936233982];
if (qn < 0.0 || 1.0 < qn) return 0;
if (qn == 0.5) return 0;
var w1 = qn;
if (qn > 0.5) w1 = 1.0 - w1;
var w3 = -Math.log(4.0 * w1 * (1.0 - w1));
w1 = b[0];
function loop(i) {
w1 += b[i] * Math.pow(w3, i);
if (i < b.length - 1) loop(++i);
};
loop(1);
if (qn > 0.5) return Math.sqrt(w1 * w3);
else return -Math.sqrt(w1 * w3);
}
wilson.rank = function(up_votes, down_votes) {
var confidence = 0.95;
var pos = up_votes;
var n = up_votes + down_votes;
if (n == 0) return 0;
var z = this.normaldist(1 - (1 - confidence) / 2);
var phat = 1.0 * pos / n;
return ((phat + z * z / (2 * n) - z * Math.sqrt((phat * (1 - phat) + z * z / (4 * n)) / n)) / (1 + z * z / n)) * 10000;
}

False Mirrors. can you help me to solve?

Here is the problem
BFG-9000 destroys three adjacent balconies per one shoot. (N-th balcony is adjacent to
the first one). After the shoot the survival monsters inflict damage to Leonid
(main hero of the novel) — one unit per monster. Further follows new shoot and so on
until all monsters
will perish. It is required to define the minimum amount of damage,
which can take Leonid.
For example :
N = 8
A[] = 4 5 6 5 4 5 6 5
answer : 33
4 * * * 4 5 6 5 - 24
4 * * * * * * 5 - 9
* * * * * * * * - 0
Can you help me to solve this problem? What is the complexity?
Problem can be solved with DP.
After first shot problem will not be circular anymore. Damage of monsters that left after attack can be calculated with DP. Lets NB is number of balconies.
Define D[n,m] for n<=m or m+4<=nas damage of monsters left on balconies b, n<=b<=m or m<=b<=n.
If n <= m < n+3 than D[n,m] = sum A[i] for n<=i<=m.
If m >= n+3 than D[n,m] =
min{ 2*D[n,i-1] + D[i,i+2] + 2*D[i+3,m] } for i in {n,...,m}.
If m < n than D[n,m] =
min{ 2*D[n,i-1] + D[i,i+2] + 2*D[i+3,m] } for i in {n,...,NB} U {1,...,m}.
Result is min{ D[i+3,NB+i-1] for i in {1,...,NB-2} }.
In third case and result indices are modulo NB.
This approach has complexity O(n^3).
It looks like the constraints of the problem are such that you can just brute force it . Basically
def go(hit):
res = 999
#base case, check if all items in hit are true.
for i in range(len(hit)):
if not hit[i]:
newhit = [x for x in hit]
newhit[i] = newhit[i-1] = newhit[(i+1)%len(hit)] = True;
damage = 0;
for j in range(len(hit)):
if not newhit[j]:
damage+=hit[j]
res = min(res, go(newhit)+damage)
You can also implement hit as a bit map and then memoize it to speed up the function.

How do you build a ratings implementation?

We have need for a "rating" system in a project we are working on, similar to the one in SO. However, in ours there are multiple entities that need to be "tagged" with a vote up (only up, never down, like an increment). Sometimes we will need to show all of the entities in order of what is rated highest, regardless of entity type, basically mixing the result sets, I guess. What data structures / algorithms do you use to implement this so that is flexible and still scalable?
Since reddit's ranking algorithm rocks, it makes very much sense to have a look at it, if not copy it:
Given the time the entry was posted A and the time of 7:46:43 a.m. December 8, 2005 B we have ts as their difference in seconds:
ts = A - B
and x as the difference between the number of up votes U and the number of down votes D:
x = U - D
Where
y = 1 if x > 0
y = 0 if x = 0
y = -1 if x < 0
and z as the maximal value of the absolute value of x and 1:
z = |x| if |x| >= 1
z = 1 if |x| < 1
we have the rating as a function ƒ(ts, y, z):
ƒ(ts, y, z) = log10 z + (y • ts)/45000

Resources