Matricial distribution - vba

I have a list of projects with hours to be consumed, distributed linearly through the months:
Hours Aug Sep Oct Nov Dec
100 20 20 20 20 20
200 40 40 40 40 40
300 60 60 60 60 60
600 120 120 120 120 120
Some projects will end in different months, so it shoudn't mark any hour in the next months (must be 0):
Hours Aug Sep Oct Nov Dec End
100 33.3 33.3 33.3 0 0 Oct
200 50 50 50 50 0 Nov
300 60 60 60 60 60 Dec
600 143.3 143.3 143.3 110 60
However, we have to keep the proportion of 20% of the sum (20% * 600 = 120. I've put 20% because we have 5 months, but it could be different percentages) on each month, so:
Hours Aug Sep Oct Nov Dec End
100 20 30 50 0 0 Oct
200 60 50 30 60 0 Nov
300 40 40 40 60 120 Dec
600 120 120 120 120 120
I have a Sudoku-like problem here, where I need to respect the proportion of the column and keep the sum of the line on each project. I've tried in many ways (VBA or functions) to make this distribution, but I've failed so far. I believe someone has crossed with this problem before, so is there a way of doing this distribution programatically? Is there a name for this kind of distribution?

See the image below. Once you have the marginal sums filled in, enter this formula into B2 and fill in the rest of the table (enter it as an array formula with ctrl+shift+enter):
=IF(MATCH($H2,$B$1:$F$1,0)<COLUMNS($B$1:B$1),0,($A2-SUM(C2:$G2))/(SUM(IF(MATCH($H$2:$H$4,$B$1:$F$1,0)<COLUMNS($B$1:B$1),0,$A$2:$A$4))-SUM(IF(MATCH($H$2:$H$4,$B$1:$F$1,0)<COLUMNS($B$1:B$1),0,C$2:$G$4)))*B$5)
The example given will not have a unique solution, so the aim with the above formula is to balance the hours by starting at the end and moving backwards in time while applying hours proportionally to how many hours are left on each project. For example, after resolving Dec, the project on row 2 still has 200 hours to allocate and the project on row 3 still has 180 hours to allocate. The formula will therefore apply 120 * 200 / (200 + 180) hours in Nov from the project on row 2 and 120 * 180 / (200 + 180) hours from the project in row 3.
This methodology assumes that all projects start at the same time. If this assumption doesn't hold, VBA would probably be the way to go. I would sort the months by the number of active projects, smallest to largest, then apply the same sort of calculation as here.

I'm not 100% sure I got your question right, but hopefully yes because it took me quite a lot of time. It actually looks deceptively easy, but is in fact quite tricky.
So let's presume we have this table:
Now if I got it right, we basically want to count, how many hours we
used up already, and now we want to distribute what we have left
depending on the unused (empty) months
So, for example, in Row 3 (100 hours) we don't want to distribute any hours, because we already used up all 100 out of 100 hours
In the next row (4), we want to distribute 115 hours (200-85) to the remaining 2 cells => which would leave us with 57.5 hours per month left on shift
and et cetera...
Under presumption, that's what you want the algorhytm to do:
Private Sub divide_time()
Dim tbl As ListObject: Set tbl = Sheets("Sheet1").ListObjects("Table1")
Dim hour_dist() As Integer
ReDim hour_dist(1 To 3)
' first we need to learn how many hours total we have available per project
For i = 1 To 3
With tbl.ListColumns(1)
hour_dist(i) = .Range(i + 1) ' we store each value into an array per project
End With
Next i
Dim current As Double
Dim sumof As Double
Dim hours_left As Double
Dim empty_counter As Integer
For i = 1 To 3
'we reset all of the counters per row
sumof = 0
empty_counter = 0
'looping through all the column values in the row
For j = 2 To 6
current = tbl.ListRows(1).Range(i, j)
sumof = sumof + current ' we get a sum of the current values in the row
If (current = 0) Then 'if there is an empty cell, we keep track of it _
(so we know into how many cells we can still divide the remaining time)
empty_counter = empty_counter + 1
End If
Next j
' so we get how many hours we have left for the project _
in comparison to how many months are free to distribute
hours_left = (hour_dist(i) - sumof)
'if we also want to store the info, _
'what month we ended on before we distribute the remaining hours
If (hours_left = 0) Then
Select Case empty_counter
Case 0
tbl.ListRows(1).Range(i, 7) = "Dec"
Case 1
tbl.ListRows(1).Range(i, 7) = "Nov"
Case 2
tbl.ListRows(1).Range(i, 7) = "Oct"
Case 3
tbl.ListRows(1).Range(i, 7) = "Sep"
Case 4
tbl.ListRows(1).Range(i, 7) = "Aug"
End Select
Else
tbl.ListRows(1).Range(i, 7) = "Dec"
End if
If (empty_counter <> 0) Then '( we dont want to be dividing by 0 )
For n = 6 To (6 - empty_counter + 1) Step -1
'for each month we divide what we have left _
depending on the % of the months available
tbl.ListRows(1).Range(i, n) = (hours_left / empty_counter)
Next n
End If
' and we loop it for each and every row
Next i
End Sub
The resulting table will look like this:

I guess you need to think "backwards".
First distribute hours among active projects in month n (December).
Deduct these hours from respective project and move to month n-1, etc.

Related

Referencing previous row value using a window function

I'd like to be able to reference a previous rows value (which is calculated using previous rows value as well) and do something to that value in the next column.
It would be something like, where "prev" is the value of the most recent computation:
select sum(price * qty + prev) over (order by date) from temp
Here is example data, note that the 4th row of entry column need reference the 3rd row of realized_pnl:
cumu_usd cumu_stock entry realized_pnl
100 1 100 0
300 5 60 0
150 3 50 (150/2 - 60) * 2 = 30
200 4 (200+30)/4 30
entry = (cumu_usd +realized_pnl) / cumu_stock

Calculate percentage between two values

I have two columns that hold numbers for which I am trying to calculate the difference in % between and show the result in another column but the results seem to be wrong.
This is the code in question.
SELECT
GenPar.ParameterValue AS ClaimType,
COUNT(Submitted.ClaimNumber) AS SubmittedClaims,
COUNT(ApprovalProvision.ClaimNumber) AS ApprovedClaims,
COUNT(Declined.ClaimNumber) AS DeclinedClaims,
COUNT(Pending.ClaimNumber) AS PendingClaims,
ISNULL(SUM(SubmittedSum.SumInsured),0) AS TotalSubmittedSumInsured,
ISNULL(SUM(ApprovedSum.SumInsured),0) AS TotalApprovedSumInsured,
ISNULL(SUM(RejectedSum.SumInsured),0) AS TotalRejectedSumInsured,
ISNULL(SUM(PendingSum.SumInsured),0) AS TotalPendingSumInsured,
--This column is to show the diff in %
CASE WHEN COUNT(Submitted.ClaimNumber) <> 0 AND COUNT(ApprovalProvision.ClaimNumber) <> 0
THEN (COUNT(ApprovalProvision.ClaimNumber),0) - (COUNT(Submitted.ClaimNumber),0)
/COUNT(Submitted.ClaimNumber) * 100
ELSE 0
END
What I need is to show the difference in % between the columns SubmittedClaims and ApprovedClaims. Any column, or both may contain zeroes and it may not.
So it's: COUNT(Submitted.ClaimNumber) - COUNT(ApprovalProvision.ClaimNumber) / COUNT(Submitted.ClaimNumber) * 100 as far as I know.
I have tried this and an example of what it does is it takes 1 and 117 and returns 17 when the difference between 1 and 117 is a decrease of 99.15%. Another example is 2 and 100. This simply returns 0 whereas the difference is a decrease of 98%.
CASE WHEN COUNT(Submitted.ClaimNumber) <> 0 AND COUNT(ApprovalProvision.ClaimNumber) <> 0
THEN (COUNT(ApprovalProvision.ClaimNumber),0) - (COUNT(Submitted.ClaimNumber),0)
/COUNT(Submitted.ClaimNumber) * 100
ELSE 0
END
I've checked this link and this seems to be what I am doing.
Percentage difference between two values
I've also tried this code:
NULLIF(COUNT(Submitted.ClaimNumber),0) - NULLIF(COUNT(ApprovalProvision.ClaimNumber),0)
/ NULLIF(COUNT(Submitted.ClaimNumber),0) * 100
and this takes for example 2 and 100 and returns -4998 when the real difference is a decrease of 98%.
For completion, Submitted.ClaimNumber is this portion of code:
LEFT OUTER JOIN (SELECT * FROM Company.Schema.ClaimMain WHERE CurrentStatus=10)Submitted
ON Submitted.ClaimNumber = ClaimMain.ClaimNumber
ApprovalProvision.ClaimNumber is this:
LEFT OUTER JOIN (SELECT * FROM Company.Schema.ClaimMain WHERE CurrentStatus=15)ApprovalProvision
ON ApprovalProvision.ClaimNumber = ClaimMain.ClaimNumber
Ideally, this column would also deal with 0's. So if one value is 0 and the other is X, the result should return 0 since a percentage can't be calculated if original number is 0. If the original value is X and the new value is 0, I should show a decrease of 100%.
This will occur across all columns but there is no need to flood the page with the rest of the columns since all calculations will occur in the same manner.
Anybody see what I'm doing wrong?
I'm not familiar with why you have (x,0) as a syntax
But I see that you have
(COUNT(ApprovalProvision.ClaimNumber),0) - (COUNT(Submitted.ClaimNumber),0)
/COUNT(Submitted.ClaimNumber) * 100
shouldn't it be,
( COUNT(ApprovalProvision.ClaimNumber) - COUNT(Submitted.ClaimNumber) )
/COUNT(Submitted.ClaimNumber) * 100
It looks like it would do count of ApprovalProvision.ClaimNumber - 100 since submitted.claimnumber divided by itself is 1 times 100 is 100.
The 4900 number actually sounds right. Lets take the following example, you have 2 apples, and then you're given 98 more and got 100 apples.
An increase of 98% would have meant from 2 apples, you would have 3.96 apples.
An increase of 100% means from 2 apples you end with 4 apples. An increase of 1000% means from 2 apples you end with 22 apples. So 4000% means you end with 82 apples. 5000% means from 2 apples, you reach 102 apples.
(100-2)/2*100 = 98 / 2 = 49 * 100 = 4900, so it looks like there is a 4900% increase in number of apples if you started with 2 apples and reach 100.
Now if you had flipped the 2 and 100, say starting with 100, now you have 2,
(2-100)/100*100 = -98, so a -98% change of apples, or a 98% decrease.
Hope this solves your problem.

Calculate rental Costs in excel-vba

Hi I'm currently trying to setup a Calculator for Rental Cars.
You'll put in the car category, the rental days, whether or not you want to book the fuel-flat and how many kilometers you'll travel, whether or not you'll need winter tires, if you're traveling to a certain destination and how many people will be in the car.
My Data-Spreadsheet currently looks like this:
http://i.imgur.com/P2kz6ts.png
So, if you would for example wanted to rent a Ford Fiesta for 2 days with 2 people, winter tires and fuel-flat for 80km to destination 2.
The calculator should now pick the price for 1-3 days and multiply it by 2 for two days.
To that the cost for winter tires will be added which are 3€ per day. Then it'll add the cost for the fuel-flat for 50 - 150 km, since you'll be traveling 80km. Since you're travveling to destination 2, the rental company is willing to give a discount of 15€. Finally the calculated cost will be divided by 2, since 2 people will rent the car and split the costs evenly.
That Calculation should look like this:
((30*2+(3*2)+13,80)-15)/2)
So in the End the total Cost of 32,4€ shall be displayed in a msgbox.
Now, how do i code that if the rental days are between 1-3, excel should take the values of that specific collumn and use it to calculate the cost. Moreover, if you're renting the car more than a week then the price for 6-7 days shall be used and additional days be added according to the category.
Problem fixed, look at the Answer.
Here's the code that fixed the Problem:
If rentaldays > 30 Then
Application.Cells(3, 3).Value = "You're trying to rent a Car for more than 30 Days. Please use the longterm program."
price = 0
End If
If rentaldays = 30 Then
monat = (Application.WorksheetFunction.VLookup(car, Sheets("Data").Range("A:K"), 7, False))
End If
If rentaldays > 7 Then
zusatz = price + (((rentaldays - 7) * Application.WorksheetFunction.VLookup(car, Sheets("Data").Range("A:K"), 6, False)))
rentaldays = rentaldays - (rentaldays - 7)
End If
If rentaldays = 6 Or rentaldays = 7 Then
woche = (Application.WorksheetFunction.VLookup(car, Sheets("Data").Range("A:K"), 5, False))
End If
If rentaldays > 3 And rentaldays <= 5 Then
tage = price + (Application.WorksheetFunction.VLookup(car, Sheets("Data").Range("A:K"), 4, False) * rentaldays)
End If
If rentaldays >= 1 And rentaldays <= 3 Then
tage = price + (Application.WorksheetFunction.VLookup(car, Sheets("Data").Range("A:K"), 3, False) * rentaldays)
End If
You need to make this easier , not "smarter"
Just add more columns!
Then you have 3 simple rules (english code)
if days >= 30 then
price = Col K 'lookup for car
days = days - 30
end if
if days > 7 then
price = price + ((days - 7) * Col J) 'lookup for car
days = days - (days - 7)
end if
if days > 0 then
price = price + hlookup( days, C:I .....) 'lookup for car
end if

Rounding to a specific number ex: 4 ,5, 9 ,0 sql

I have prices that are going to be converted from one currency to several other currencies but once they are converted I would like to round them to a specific number.
The examples on what I need to round are the following :
Anything under 10 round to the next digit. For this I can just use the CEILING function.
Anything between 10-14 needs to rounded to 14.00. ex: 12.78 to 14.00
Between 14.01 and 15 needs to rounded to 15.00. ex: 14.25 to 15.00
Between 15.01 and 19 needs to be rounded to 19.00 ex: 17.35 to 19.00
Between 19.01 and 20 needs to rounded to 20.00. ex: 19.25 to 20.00
I know this seems a little weird but it is a specification I've been given for my project. To round to the next multiple of 5 I also understand but it is the 4 and 9 values that are really stumping me.
What formula would I need to use to obtain these numbers, or would it be easier to explode the number, grab the value before the decimal and do a case based on the criteria I stated above?
Thanks for your help!
A case seems to make the most sense but your "rounding" logic may look a little strange:
CASE
WHEN value < 10 THEN CEILING(value)
WHEN value <= 14 THEN 14
WHEN value <= 15 THEN 15
WHEN value <= 19 THEN 19
WHEN value <= 20 THEN 20
END
Or you could convert from floats to ints and use the modulus (%) operator:
CASE
WHEN value < 10 THEN CONVERT(int,CEILING(value))
WHEN CONVERT(int,CEILING(value)) % 5 = 0 -- 9.01 - 10, 14.01 - 15, 19.01 - 20, etc.
THEN CONVERT(int,CEILING(value))
WHEN CONVERT(int,CEILING(value)) % 5 <= 4 -- 10.01 - 14, 15.01 - 19, etc.
THEN CONVERT(int,FLOOR(value / 5)) * 5 + 4
END
Since you have slightly odd requirements (there is no simple formula to cover all your cases), then I think your best bet is to use a CASE...WHEN statement like this. Obviously tweak the precise inequalities based on your requirements:
SELECT CASE WHEN colVal < 10 THEN CEILING(colVal)
WHEN colVal <= 14 THEN 14
WHEN colVal <= 15 THEN 15
WHEN colVal <= 19 THEN 19
WHEN colVal <= 20 THEN 20
ELSE someotherval
END
EDIT: Based on the clarified requirements, D Stanley's answer with the modulo stuff is a better fit.

BlackJack Project - Can't make aces change from 11 to 1's

I'm currently making a blackjack game for my project in school in Visual Basic.
In blackjack, when you have aces (value initially 11) their value turns to 1 when the total value of the cards is > 21. In code, this would just take away 10 for every ace
I'm stuck on this.
This is the code I have (that doesn't work):
Do While PlayerValue > 21 And counter <= noAcesPlayer
counter += 1
PlayerValue -= 10
Loop
In a senario, I have a: 2, 8, A, 8 (=29)
But since there is an Ace, and the total value is > 21, the value should have 10 subtracted from it (=19) - the above code does not do this.
Another scenario would be 10, 8, A, A (=40)
Again, the two Aces should turn into 1's, since the total value > 21, giving 20.
Any help would be greatly appreciated. :)
Here is an approach
Public Enum CardFace
None
Ace
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Jack
Queen
King
End Enum
This code should produce a value of twenty
Dim cards As New List(Of CardFace) From {CardFace.Ten, CardFace.Eight, CardFace.Ace, CardFace.Ace}
Dim total As Integer = 0
Dim numofAces As Integer = 0
For Each c As CardFace In cards
Debug.WriteLine(c.ToString)
If c = CardFace.Ace Then
numofAces += 1
Else
total += c
End If
Next
If numofAces > 0 Then
If total + 11 + (numofAces - 1) > 21 Then
total += numofAces
Else
total += 11 + (numofAces - 1)
End If
End If
Debug.WriteLine(total)
The correct way to build a blackjack hand is the following (in pseudo-code):
Variables: total = 0, soft-flag = false
For each card in hand:
Add card value to total. Faces are 10, aces are 1.
If the card you added was an ace, set soft-flag = true
If total < 12 and soft-flag:
Add 10 to total
Else:
set soft-flag = false
That's it. Only one loop over the cards, no extraneous variables, and you're left with the total value and a flag indicating if the total is soft.