Method for recursively calculating values from a PHP array? - php

This question is somewhat related to hierarchial data but I already have a stored procedure from my database which is returning the data mentioned below. I am not trying to find out how to build a hierarchy (done), I am trying to figure the best way to calculate numbers based on that hierarchy with PHP (perhaps recursive arrays?).
Using a custom stored procedure, I am able to pull a list of sales representatives for our company, the products they sold and the "earn" percentage for each one of those products on new rows and their subsequent sales reps (if in a tree).
An Example of the data returned can be as follows:
Repnum | Name | productID | sales | earn | depth | parentRep
------------------------------------------------------------
1 | A | 1 | 5000 | 0.50 | 1 | 0
1 | A | 2 | 10000 | 0.35 | 1 | 0
2 | B | 1 | 400 | 0.40 | 2 | 1
2 | B | 2 | 1000 | 0.30 | 2 | 1
7 | E | 1 | 30000 | 0.35 | 3 | 2
3 | C | 1 | 5000 | 0.33 | 2 | 1
It is safe to assume that the order of the data returned is already formatted and sorted appropriately based on the depth and information not presented here. In a Tree view, the data above would look like this:
1-A-1-5000-0.50-1-0
1-A-2-10000-0.35-1-0
2-B-1-400-0.40-2-1
2-B-2-1000-0.30-2-1
7-E-1-30000-0.35-3-2
3-C-1-5000-0.33-2-1
In my while(results) loop, I am trying to calculate the following for EACH repnum returned:
The total of that rep num's sales multipled by the earn % for each product (their total sales and earn amounts)
The total amount of sales for each downline tree multiplied by the difference between the current rep and the level below it
To show the math for how this would work:
Rep 1 Earnings:
Rep 1 total sales = 5000 + 10000 = 15,000
Rep 1 earn on self = (5000 * 0.50) + (10000 * 0.35) = 6,000 hello!
Rep 2 total sales = 400 + 1000 = 1400
Rep 1 earn on rep 2 sales = (400 * (0.50-0.40) + (1000 * (.35-0.30)) = 90
Rep 7 total sales = 30000
Rep 1 earn on rep 7 sales = (30000 * (0.50-0.40)) = 3000*
(* the reason the earn is calculated at rep 1 earn - rep 2 earn is because in any tree, the "parent" can only ever earn the difference of his/her earn and the direct depth below him/her)
Rep 3 total sales = 5000
Rep 1 earn on rep 3 sales = (5000 * (0.50-0.33)) = 850
**Rep 1 TOTAL earn = 6,000 + 90 + 3000 + 850 = 9,940**
Rep 2 Earnings:
Rep 2 total sales = 400 + 1000 = 1400
Rep 2 total earn on self = (400 * 0.40) + (1000 * 0.30) = 460
Rep 7 total sales = 30000
Rep 2 total earn on rep 7 sales = (30000 * (0.40-0.35)) = 1500*
(* here, rep 2 earns the difference between him/her and rep 7 because he is in the depth right above it / his/her parent)
**Rep 2 TOTAL earn = 460 + 1500 = 1,960**
... and so on
I could have built the script to use HEAVY mysql recursion and simply done a hefty number of while() loops for each depth but found it unnecessary and taxing on a system given the stored procedure I use to go ahead and pre-calculate the hierarchy and depth and sort the data appropriately. Calculating the top level rep is simple enough but to then go back from the 2nd person on the list (and so on) and start again is where I am struggling some.
I would like to be able to return an output similar to the following based on the sample data above:
num | Name | earnAmount
------------------------
1 | A | 9940
2 | B | 1960
7 | E | 10500
3 | C | 1650
Thank you ahead of time for any help!
Notes on questions that have been asked:
Q: How are earn % calculated?
A: They aren't calculated, they are determined by the rep the particular productID that rep is selling.
Q: How are you determining "levels" or parent/child relationships?
A: I am not in this example. This piece of the equation is taken care of through a MySQL stored procedure and in a lot of ways not relevant (I could also display the parent repnum for each rep but since the array is built from the top down, it likely is not too helpful). The depth and sort order of the example data in the first table is already formatted and laid out in a way such that each tree could be printed by a simple print(results_array) statement in PHP.
Q: What is the relevance of productID in the first table?
A: Each rep can sell any product available in our system (hundreds), classified by a productID. Each rep also earns a percentage of each sell for that specific product. The earn % is completely unrelated to a specific product type (though there are max and min earns for each product) or a particular depth (though a rep on a higher depth never has a lower earn % for a specific productID than his downline tree).
Q: How is your data sorted in the first table?
A: Somewhat irrelevant (just trust me) but behind the scenes I create a breadcrumb column which takes the current repnum, then appends children and sorts based on a combination of it and the depth of the tree at that point. Example as given:
0 (invisible parent which selects ALL sales reps)
0-1
0-1-2
0-1-2-7
0-1-3
...

Here is my approach.
I assumed that the array can be one-dimensional. depth is just enough to let us determine if a Rep has "children". So the array looks like that:
$reps = array(
array("rep" => "1", "name" => "A", "productId" => "1", "sales" => 5000, "earn" => 0.50, "depth" => "1"),
array("rep" => "1", "name" => "A", "productId" => "2", "sales" => 10000, "earn" => 0.35, "depth" => "1"),
array("rep" => "2", "name" => "B", "productId" => "1", "sales" => 400, "earn" => 0.40, "depth" => "2"),
array("rep" => "2", "name" => "B", "productId" => "2", "sales" => 1000, "earn" => 0.30, "depth" => "2"),
array("rep" => "7", "name" => "E", "productId" => "1", "sales" => 30000, "earn" => 0.35, "depth" => "3"),
array("rep" => "3", "name" => "C", "productId" => "1", "sales" => 5000, "earn" => 0.33, "depth" => "2")
);
I decided to take a recursive approach. We loop through the array in the earn() function, advancing the array pointer after each iteration. Inside the function we iterate again in a for loop starting from the current + 1 array element to the end to find the Rep's "children". The code looks like that:
function earn() {
global $reps, $earns;
$rep = current($reps);
$key = key($reps);
$immediateChildEarn = null;
//basic Rep's earnings
$earn = $rep['sales'] * $rep['earn'];
//loop to find children with the same productId
for ($i = $key + 1; $i < count($reps); $i++) {
$child = $reps[$i];
//we're only interested in Reps with the same product and deeper position
if ($rep['productId'] !== $child['productId'] || $rep['depth'] >= $child['depth']) {
continue;
}
//collect the earn of the immediate child
if (null === $immediateChildEarn) {
$immediateChildEarn = $child['earn'];
}
//the earn difference can't be greater than the difference between Rep and its first immediate child Rep
if ($immediateChildEarn > $child['earn'] && $rep['depth'] + 1 < $child['depth']) {
$child['earn'] = $immediateChildEarn;
}
//calculate the earnings gained from this child
$earn += $child['sales'] * ($rep['earn'] - $child['earn']);
}
//just a quick fix to prevent throwing Notices - not significant for the algorithm itself
if (!isset($earns[$rep['rep']])) {
$earns[$rep['rep']] = 0;
}
$earns[$rep['rep']] += $earn;
$finish = next($reps);
if (false !== $finish) {
earn();
}
}
$earns = array();
reset($reps);
earn();
The results of var_dump($earns) will be:
Array
(
[1] => 9940
[2] => 1960
[7] => 10500
[3] => 1650
)
Please, feel free to comment my answer. I'll try to repair any mistakes and improve the code the best I can.
Complexity:
I'm not good at calculating complexity of an algorithm but in my solution the complexity would be I think:
time complexity
worst-case O(n logn)
best-case O(2n)
memory complexity (not including input array)
worst-case O(n)
best-case O(1)
If I'm wrong, please feel free to correct me.

Related

Scoring system for collection of numbers (serial#)

I'm looking for a formula (PHP) that I can use to assign a score base on rarity of a number inside a collection. Serial#1 being the rarest.
For example, I have few sets of collections.
Collection 1 - Total number of items = 10 (Serial #1 to Serial #10)
Collection 2 - Total number of items = 100 (Serial #1 to Serial #100)
Collection 3 - Total number of items = 3500 (Serial #1 to Serial #3500)
Based on the 3 example sets. Collection 1 is considered the rarest collection because of only 10 items available in the set.
In addition to this. Each item is assigned with its serials, #1 being the best (rarest)
Here is the table of how I visualize the scoring system.
Collection 1
| Serial#| Score |
|:------:| :-----:|
| 1 | 1000 |
| 2 | 900 |
| 3 | 800 |
| 4 | 700 |
| 5 | 600 |
| 6 | 500 |
| 7 | 400 |
| 8 | 300 |
| 9 | 200 |
| 10 | 100 |
Collection 2
| Serial#| Score |
|:------:| :----:|
| 1 | 800 |
| 2 | 700 |
| 3 | 600 |
| 4 | 500 |
| ... | ... |
| 99 | 12 |
| 100 | 10 |
I just made up those scores just for representation.
With the tables above, Serial #1 in Collection 1 has a higher score compared to Serial #1 in Collection 2 because of the rarity of Collection 1.
I have few collections ranging from 1 of a kind (rarest of them all),10, 20, 150, 350, 1000, 5000, 10000, 50000” What “score” is this item supposed to get then?
for the rarest 1 of 1 the score will be based on the score of the
other Serial #1. If for example Collection with 10 items the serial#
get 1000 points then the 1 of 1 i can give 5000 points (this one no
need to calculate)
2. Are all the scores inside a collection supposed to add up to the same value?
No. It doesn't need to add up to the same value as the other collections
Your “made up” scores aren’t really helpful in explaining what kind of scoring logic you want to apply here in the first place
In the example table (made up scores). I just want to show that the different serial number scores. The higher serial number will have a lower score compare to the lower serial#. But the scores will differ from the other collections with the same serial number. Thus I categorized them by collections. Lower item count collections are considered rarer than those with higher item count.
4. But Serial #1 is supposed to have a higher score, than Serial #2, inside the collection? If so, then what would that be based on? Just the ascending order of those #1, #2, etc.?
Maybe it will be based on the ascending order.
5. All 10 items in collection 1 could get the same score?
No.
I don't have any preference on the score the serial number will get. I mean Serial #1 can get X score as long as it will be relative to the rarity of the collection. Serial #1 score will not be the same across collection unless they belong to the same collection rarity.
If I understand you correctly you want to count occurrences of score (value) and sort that occurrence count in a way that the lowest number (serial) will represent the values (score) that are the rarest and the higher the serial number is the value is more common.
For the:
Input Colleciton:
Array
(
[0] => 5
[1] => 4
[2] => 3
[3] => 2
[4] => 1
[5] => 10
[6] => 10
[7] => 11
[8] => 11
[9] => 12
[10] => 12
[11] => 13
[12] => 13
[13] => 14
[14] => 14
[15] => 100
[16] => 100
[17] => 100
[18] => 101
[19] => 101
[20] => 101
[21] => 102
[22] => 102
[23] => 102
[24] => 103
[25] => 103
[26] => 103
[27] => 104
[28] => 104
[29] => 104
)
this code:
<?php
header('Content-Type: text/plain');
// generate colleciton
$collection = [];
# most unique values
$start = 5;
$end = 0;
for ($i = $start; $i > $end; $i--) {
$collection[] = $i;
}
# less unique values (2x the same values)
$start = 10;
$end = 15;
for ($i = $start; $i < $end; $i++) {
$collection[] = $i;
$collection[] = $i;
}
# least unique values
$start = 100;
$end = 105;
for ($i = $start; $i < $end; $i++) {
$collection[] = $i;
$collection[] = $i;
$collection[] = $i;
}
echo "Input Colleciton:\n";
print_r($collection);
# array of [value => how_many_occurences, ... => ...]
$valueCount = array_count_values($collection);
echo "Value count:\n";
print_r($valueCount);
$uniqueBins = [];
# convert to unique bins
foreach ($valueCount as $value => $key) {
$uniqueBins[$key][] = $value;
}
echo "Unique bins:\n";
print_r($uniqueBins);
// optionally sort by value
foreach ($uniqueBins as $key => $bin) {
asort($bin);
$uniqueBins[$key] = $bin;
}
echo "Unique bins after sort:\n";
print_r($uniqueBins);
generates the output:
Unique bins after sort:
Array
(
[1] => Array
(
[4] => 1
[3] => 2
[2] => 3
[1] => 4
[0] => 5
)
[2] => Array
(
[0] => 10
[1] => 11
[2] => 12
[3] => 13
[4] => 14
)
[3] => Array
(
[0] => 100
[1] => 101
[2] => 102
[3] => 103
[4] => 104
)
)
Where values from the array under the key 1 are rarest and values from the array under the key 3 are most common.
The key number is not starting from zero but from 1 because values that are rarest have only single (1) occurrence in the whole collection.
If you want the output bin array to have keys starting from zero and continues in numbering (0,1,2,...) then use array_values(), this way:
$uniqueBins = array_values($uniqueBins);
at the end. By doing that you loose the information about the count (rarity) of values however you still have arrays of values in the order of their rarity (starting from most to the least)
To make this code work you need to have all values into single array $collection but if you have your values in multiple arrays then you can merge all of them into single $collection by using array_merge
Here are headlines to get you started....
Create a DB
Like this
Category :Rarest
uniqueid item name score
hashed#125 Dragon 1000000
hashed#122 Hydra 800000
hashed#100 Medusa 750000
Category :Rarer
uniqueid item name score
hashed other1 50000
hashed other2 30000
hashed other3 10000
Category :Rare
uniqueid item name score
hashed other1 5000
hashed other2 2000
hashed other3 800
This is how you can approach this...
0.hidden readonly checkbox[contains uniquehash] associate with clickable images[showing name of item]
1.[Connect to DB] mysql here
2.[query from tables (all uniqueids 1,2,3 and...)]
3.[from Post get user inputs as array][say user click image showing medusa and hydra - the hidden checkboxes send hashed#100 and hashed#122]
4.[foreach DB values as #strings]( unique ids are broken to individual strings)
5.[foreach user array as #strings]( uniqueids from userinputs are broken to individual strings)
6.[if user string match DB #strings](match each hash]
7.[return query [score] and [name of item] from where #string is found ](only matching ids return their score in this case score 800000 and 750000 as well as Hydra + Medusa)
8.[then [score for match1]] + [score for match2] + match 3 and so on]
(800000+750000)
echo [total score for user selections] + [names of items]
(1.55mil,hydra + medusa ) you can even show they are from the rarest collection
Since the beginning of the process the user in the frontend does not know what the item score is and there is no way to know they just get the total

How to record this value in mysql database?

I have a data that looks like this
"discountPrices": [
{
"startQuantity": 3,
"price": 65.0
},
{
"startQuantity": 8,
"price": 62.0
},
{
"startQuantity": 20,
"price": 60.0
},
]
As you know, this is a discount price of a product. I am trying to achieve two things here.
1.) I want to create a table called discountprice and the table will have columns namely price | minqty | maxqty. Using the data above, the first row will be 65.00 | 3 | 7 , and the second row will be 62.00 | 8 | 19 and the third row will be 60 | 20 | 1000.
The issue I am first here is that I don't know how to get the maximum qty because the data did not contain the maximum quantity. It only states the startQuantity. How can I get the Maxquanity for each of the row and record it?
2.) How can I output it on a page. For example, using the data, I can do something like 3>=7 = $65.00, 8>=19 = $62.00 , 20>=1000 = $60.00
I think the hardest part is how to know the maximum quantity since it is not specified. Please how can I achieve this result.
You don't need to know the max qty. Just store your data as is and query for the first row that has a startQuantity less than or equal to your orderQuantity:
SELECT price FROM discounts
WHERE startQuantity <= :orderQuantity
ORDER BY startQuantity DESC
LIMIT 1
See here for example.
If you don't find a result, then there's no discount and your price is just the base undiscounted price. (Or you could insert a row with the base price and 0 for startQuantity.)
Note, I assume you're selling more than one product, so you'll likely need a field in that table (and the associated WHERE clause) to identify which product.
You can use the following code snnipt to get the max quantity
$json = '[
{
"startQuantity":3,
"price":65.0
},
{
"startQuantity":8,
"price":62.0
},
{
"startQuantity":20,
"price":60.0
}
]';
$jsonToArray = json_decode($json, true);
$startQuantity = array_column($jsonToArray, 'startQuantity');
array_walk($jsonToArray, function($v, $k) use (&$jsonToArray, $startQuantity) {
$index = array_search($v['startQuantity'], $startQuantity);
if(array_key_exists($index+1, $startQuantity))
$jsonToArray[$k]['endQuantity'] = $startQuantity[$index+1]-1;
});
print_r($jsonToArray);
Result
Array
(
[0] => Array
(
[startQuantity] => 3
[price] => 65
[endQuantity] => 7
)
[1] => Array
(
[startQuantity] => 8
[price] => 62
[endQuantity] => 19
)
[2] => Array
(
[startQuantity] => 20
[price] => 60
)
)

PHP conditional loop to perform operations like an order matching engine

I'm trying to build a mock asset exchange in PHP. For a given asset, XYZ, below is the orderbook:
| buy_price | amount | sell_price | amount |
|-----------|--------|------------|--------|
| 99.3 | 100 | 99.6 | 110 |
| 99.2 | 150 | 99.7 | 170 |
| 99.1 | 125 | 99.8 | 200 |
Now if a user places a buy order of 400 units at market price, the orders will be executed as follows:
110 units filled at 99.6
170 units filled at 99.7
120 units filled at 99.8
How would I conditionally loop through open sell orders so that the buy order of 400 units are filled at different existing sell order prices in orderbook? I'd also like to store the executed trades arrays like:
$trade1 = ["amount" => 110, "price" => 99.6]
$trade2 = ["amount" => 170, "price" => 99.7]
$trade3 = ["amount" => 120, "price" => 99.8]
You really just need to keep track of how many you've credited towards the buy order as you loop through. Then, instead of what you show as "I'd also like to store the executed trades arrays like", you use a single multi-dimensional array as opposed to multiple arrays.
// mock of ordered result from DB
$sellOrders = array(
array("sell_price" => 99.6, "amount" => 110),
array("sell_price" => 99.7, "amount" => 170),
array("sell_price" => 99.8, "amount" => 200),
);
// this is where we keep track of total bought/sold
$purchasedCount = 0;
// this is the buy order count
$orderCount = 400;
// this is our result summary of the trades executed
$trades = array();
foreach($sellOrders as $sell)
{
$thisPurchase = 0;
if($orderCount - $purchasedCount >= $sell['amount'])
{
$thisPurchase = $sell['amount'];
$purchasedCount += $sell['amount'];
}
else
{
$thisPurchase = $orderCount - $purchasedCount;
$purchasedCount = $orderCount;
}
$trades[] = array("amount" => $thisPurchase, "price" => $sell['sell_price']);
if($purchasedCount == $orderCount)
{
break;
}
}
var_dump($trades);
DEMO

Getting all ancestors of an element from a table

I am building a badges app in CodeIgniter using a MySQL database. There are 3 types of badges: level 1, level 2 and level 3 badges.
Each level 2 badge is awarded once you get all the required level 1 badges and each level 3 badge is awarded once you get all the required level 1 and level 2 badges. There is also a level 3 badge that requires getting 4 level 3 badges.
I have the following database table for my badges relations:
badge_id - children_id
1 - 20
1 - 25
20 - 40
20 - 45
26 - 40
25 - 39
40 - 50
I need a function that returns me all the ancestors of a badge.
For example, if the function recieved the argument 50 it would return: 40, 26, 20, 1. Any ideas?
If you want to handle it by pure mysql, you need to declare a mysql stored procedure which has some depth limitations.
Instead of that I would recommend using a simple recursive PHP function to return what you need:
function get_ancestors($child_id, $badges)
{
$found = array();
foreach($badges as $k => $row){
if ($row['children_id'] == $child_id){
$found = array_merge($found, get_ancestors($row['badge_id'], $badges));
$found[] = $row['badge_id'];
}
}
return $found;
}
The $badges variable holds all the rows from your badges relations table, just simply read it out from the database.
Based on the example you gave, for the following call
get_ancestors(50, $badges);
the output would be:
Array
(
[0] => 1
[1] => 20
[2] => 26
[3] => 40
)

How to print specific array entry using variable for position e.g. $array[$x]

With help from others on here, I've got a nested loop on the go that pull a list of months from one sql table and then, for each of those months, it goes through an events table and pulls the respective events.
Table structures are along the lines of:
MonthTable
ID | MonthShort | MonthLong
1 | 2012Oct | October 2012
2 | 2012Sep | September 2012
EventTable
ID | MonthID | Event | Guests | Adults | Children
1 | 1 | Wedding | 200 | 150 | 50
2 | 1 | Bar Mitzvah | 100 | 50 | 50
3 | 1 | Funeral | 100 | 50 | 50
4 | 2 | Birthday | 50 | 30 | 20
5 | 2 | Birthday | 300 | 200 | 100
6 | 2 | Wedding | 200 | 180 | 20
My loop works so that it populates menu A with all available months, then populates menu B with all of the events for that month. You can then click on the event and it displays the relevant information - this is where I'm a bit stuck.
The arrays I've got are similar to the following, the guests array is what I'm trying out atm:
$events = array();
$months = array();
$guests = array();
while ($row = mysql_fetch_array($result)) {
$months[$row["MonthID"]] = $row["MonthLong"];
$events[$row["MonthID"]][] = $row["Event"];
$guests[$row["MonthID"]][] = $row["Guests"];
}
I use a foreach to populate menu B with ($events[$x] as $event). The screen for each event will have an entry similar to the following and this is what I'd like to do (obviously I know this won't work bu it should serve for illustrative purposes):
echo ' Number of guests: ' . print_r($guests[$x])
With guests and events both on the same counter I though it would allow me to print the array entry in the relevant position.
So what I'd like it if you click on "October 2012" and then select "Funeral", the screen would say:
Number of guests: 100
There are actually several dozen records per event but no point going into all of them...
Apologies for the rambling and if this makes no sense! I'm new to PHP and am only really stuck on this bit.
SQL query is built on the following:
$sql = "
SELECT
a.id, b.id AS monthId, a.event, b.monthshort, b.monthlong
FROM
events_table_name AS a
INNER JOIN
month_table_name AS b ON b.id = a.monthId
ORDER BY
b.id, a.id ASC
";
You need make use of the index in the foreach statement. I mean
foreach ($events[$x] as $i => $event) {
...
echo ' Number of guests: ' . print_r($guests[$x][$i]);
}
I would go for a different data structure in PHP. How about this? You might have to change your SQL query to get it, but this is the data structure I'd aim for:
$months = array(
'1' => array(
'long' => 'October 2012',
'events' => array(
'1' => array(
'name' => 'Wedding',
'guests' => '200'
),
'2' => array(
'name' => 'Bar Mitzvah',
'guests' => '100'
),
'3' => array(
'name' => 'Funeral',
'guests' => '100'
)
)
),
'2' => array(
// etc.
)
);
This way, you're able to look up a month; for each month, its events; for each event, its attendance and name.

Categories