I want to calculate and store the dense rank and gapped rank for all entries in an array using PHP.
I want to do this in PHP (not MySQL because I am dealing with dynamic combinations 100,000 to 900 combinations per week, that’s why I cannot use MySQL to make that many tables.
My code to find the dense ranks is working, but the gapped ranks are not correct.
PHP code
$members = [
['num' => 2, 'rank' => 0, 'dense_rank' => 0],
['num' => 2, 'rank' => 0, 'dense_rank' => 0],
['num' => 3, 'rank' => 0, 'dense_rank' => 0],
['num' => 3, 'rank' => 0, 'dense_rank' => 0],
['num' => 3, 'rank' => 0, 'dense_rank' => 0],
['num' => 3, 'rank' => 0, 'dense_rank' => 0],
['num' => 3, 'rank' => 0, 'dense_rank' => 0],
['num' => 5, 'rank' => 0, 'dense_rank' => 0],
['num' => 9, 'rank' => 0, 'dense_rank' => 0],
['num' => 9, 'rank' => 0, 'dense_rank' => 0],
['num' => 9, 'rank' => 0, 'dense_rank' => 0]
];
$rank=0;
$previous_rank=0;
$dense_rank=0;
$previous_dense_rank=0;
foreach($members as &$var){
//star of rank
if($var['num']==$previous_rank){
$var['rank']=$rank;
}else{
$var['rank']=++$rank;
$previous_rank=$var['num'];
}//end of rank
//star of rank_dense
if($var['num']===$previous_dense_rank){
$var['dense_rank']=$dense_rank;
++$dense_rank;
}else{
$var['dense_rank']=++$dense_rank;
$previous_dense_rank=$var['num'];
}
//end of rank_dense
echo $var['num'].' - '.$var['rank'].' - '.$var['dense_rank'].'<br>';
}
?>
My flawed output is:
num
rank
dynamic rank
2
1
1
2
1
1
3
2
3
3
2
3
3
2
4
3
2
5
3
2
6
5
3
8
9
4
9
9
4
9
9
4
10
Notice when the error happens and there is a higher number in the number column it corrects the error in that row. See that when the number goes from 3 to 5.
Given that your results are already sorted in an ascending fashion...
For dense ranking, you need to only increment your counter when a new score is encountered.
For gapped ranking, you need to unconditionally increment your counter and use the counter value for all members with the same score.
??= is the "null coalescing assignment" operator (a breed of "combined operator"). It only allows the right side operand to be executed/used if the left side operand is not declared or is null. This is a technique of performing conditional assignments without needing to write a classic if condition.
Code: (Demo)
$denseRank = 0;
$gappedRank = 0;
foreach ($members as &$row) {
$denseRanks[$row['num']] ??= ++$denseRank;
$row['dense_rank'] = $denseRanks[$row['num']];
++$gappedRank;
$gappedRanks[$row['num']] ??= $gappedRank;
$row['rank'] = $gappedRanks[$row['num']];
// for better presentation:
echo json_encode($row) . "\n";
}
Output:
{"num":2,"rank":1,"dense_rank":1}
{"num":2,"rank":1,"dense_rank":1}
{"num":3,"rank":3,"dense_rank":2}
{"num":3,"rank":3,"dense_rank":2}
{"num":3,"rank":3,"dense_rank":2}
{"num":3,"rank":3,"dense_rank":2}
{"num":3,"rank":3,"dense_rank":2}
{"num":5,"rank":8,"dense_rank":3}
{"num":9,"rank":9,"dense_rank":4}
{"num":9,"rank":9,"dense_rank":4}
{"num":9,"rank":9,"dense_rank":4}
For the record, if you are dealing with huge volumes of data, I would be using SQL instead of PHP for this task.
It seems like you want the dynamic rank to be sequential?
Your sample data appears to be sorted, if this remains true for your real data then you can remove the conditional and just increment the variable as you assign it:
//start of rank_dense
$var['dense_rank']=++$dense_rank;
//end of rank_dense
It sounds like you're saying you won't be implementing a database.
Databases like MySQL can easily handle the workload numbers you outlined and they can sort your data as well. You may want to reconsider.
Related
Hello here is an array of all values possibilities that should be an instance of the int we have as rank. But I would like this array to be empty first and find those values without if statements, so with a smart mathematical relation.
Example: a player who has the rank 3 would have those rewards: First time: At pos 0: 10 keys; pos 1: 13 keys; pos 2: 13 keys; 8 lives and 2 keys at pos 0 if he already registered their first time rewards.
(In fact there would be if satement only if the player never registered their rewards).
So I've tried to do this with digital sequences that I learned to school but it does not seems working.
Thank you for your time... and your help !
P.S.: I'm quite new here, sorry for any mistake.
Here is the schema of all possibilities stored in this array:
$rewards = [
1 => [
"count" => [7, 7, 7],
"lives" => 8,
"count_back" => [2, 2]
],
2 => [
"count" => [7, 10, 10],
"lives" => 10,
"count_back" => [1]
],
3 => [
"count" => [10, 13, 13],
"lives" => 13,
"count_back" => [2]
],
4 => [
"count" => [10, 13, 13],
"lives" => 15,
"count_back" => [2]
],
5 => [
"count" => [14, 15, 15],
"lives" => 18,
"count_back" => [2]
],
6 => [
"count" => [16, 18, 18],
"lives" => 20,
"count_back" => [2, 2]
]
];
So the only input we have for this command is the player rank (int). Starting to it I would like to find without if statements, that will be too big or this complete array we have above that would take too much datas in memory for nothing (because the player doesn't have several ranks) ; the specific reward.
For example: I have the input 1. So the output should be ["count" => [7, 7, 7], "lives" => 8] if the player never used the command. If he already used it so the output should be only ["count" => [2, 2]]
Application to Distribute Stock between Warehouses
I have two arrays,
One has list of Warehouses along with the Current Quantity: (This can be Dynamic with one or more locations)
[
['location_name' => 'Toronto', 'current_qty' => 3],
['location_name' => 'Mississauga','current_qty' => 7],
['location_name' => 'London', 'current_qty' => 5],
]
The Other array has the Amount of Stock that would be Comming in:
[
'qty' => 5
]
And want to distribute the Qty among the locations so that the current qty of each of the locations would be near equal to each other. So want to return an array with the number that needs to be added to each location. Like: Here , of the 5, 3 went to Toronto and 2 to London. So it can be seen then after the nearest equalization, rest of the distribution can be done randomly.
[
['location_name' => 'Toronto', 'add_qty' => 3],
['location_name' => 'Mississauga','add_qty' => 0],
['location_name' => 'London', 'add_qty' => 2],
]
And Just cannot figure out the logic of this algorithm. Would really appreciate any pointers. Many Thanks.
I would do it like this, not really sure about any performance issues. I don't know how big your data set is.
$locations = [
['location_name' => 'Toronto', 'current_qty' => 3, 'add_qty' => 0],
['location_name' => 'Mississauga', 'current_qty' => 7, 'add_qty' => 0],
['location_name' => 'London', 'current_qty' => 5, 'add_qty' => 0],
];
$supplies = 5;
// This function sorts locations, by comparing the sum of the current quantity and the quantity the location will get.
$locationsByQuantityAscending = function ($locationA, $locationB) {
return ($locationA['current_qty'] + $locationA['add_qty']) - ($locationB['current_qty'] + $locationB['add_qty']);
};
// Sort the locations, getting the ones with the lowest quantity first.
usort($locations, $locationsByQuantityAscending);
// Keep dividing, until we're out of supplies
while ($supplies > 0) {
$locations[0]['add_qty']++; // Add one to the location with the lowest supplies
$supplies--; // Decrease the supplies by one
usort($locations, $locationsByQuantityAscending); // Sort the locations again.
}
print_r($locations);
At the end, this will output:
Array
(
[0] => Array
(
[location_name] => Toronto
[current_qty] => 3
[add_qty] => 3
)
[1] => Array
(
[location_name] => London
[current_qty] => 5
[add_qty] => 2
)
[2] => Array
(
[location_name] => Mississauga
[current_qty] => 7
[add_qty] => 0
)
)
If you really need to be performant, you could also just sort the locations once by their current quantity. Then keep adding to the first location, until its stock will be higher than the second location. Then, until the quantity at the second location is higher than the third location, add one to the first and second location, etc.
I think this will be more performant, since you don't have to sort all your locations X times (X being the number of supplies to divide). I'll leave that implementation to you.
Hint: have a look at recursive functions
If performance is critical, and the input data is usually bigger then shown here (e.g. a lot more locations or a lot more quantity to distribute). You might want to consider using SplMinHeap:
For example:
<?php
$helper = new class extends SplMinHeap
{
public function compare($a, $b): int
{
return ($b['current_qty'] + $b['add_qty']) <=> ($a['current_qty'] + $a['add_qty']);
}
};
$locations = [
['location_name' => 'Toronto', 'current_qty' => 3, 'add_qty' => 0],
['location_name' => 'Mississauga', 'current_qty' => 7, 'add_qty' => 0],
['location_name' => 'London', 'current_qty' => 5, 'add_qty' => 0],
];
foreach ($locations as $entry) {
$helper->insert($entry);
}
$qty = 10000;
while ($qty-- > 0) {
$min = $helper->extract();
$min['add_qty']++;
$helper->insert($min);
}
print_r(iterator_to_array($helper));
https://3v4l.org/nDOY8
I am cracking my brain and can't find a good solution for my problem. I am trying to design a system that I can use for batch picking in our order system.
The point is that from a set of orders I want to pick 6 orders that are most equal to each other. In our warehouse most orders are them so we can safe a lot of time by picking some orders at the same time.
Assume I have the following array:
<?php
$data = [
156 => [
1,
2,
7,
9,
],
332 => [
3,
10,
6
],
456 => [
1,
],
765 => [
7,
2,
10,
],
234 => [
1,
9,
3,
6,
],
191 => [
7,
],
189 => [
7,
6,
3,
],
430 => [
10,
9,
1,
],
482 => [
1,
2,
7,
],
765 => [
1,
5,
9,
]
];
?>
The array key is the order id, and the values are the product ID's it contains. If I want to pick the top 3 orders which look at much like each other, where do I start?
Any help would be much appreciated!
1. Step
Sort productId inside order (ASC)
2. Step
In loop check difference (array_diff) in each order to each other.
Create array with defference. For example:
$diff = [
'156' => [ //order id
'234' => 4, // with order 234 has 4 differences
'332' => 7, // with order 332 has 7 differences
// and so on...
],
]
3. Step
Order $diff by ASC and receive order with less differences.
Improvement
Also you could add total size of products in order for compare with difference. For example, If you have an order with 100 products and 10 diffs - it's better than order with 10 products and 9 diffs.
Here is what i would do if I had the problem :
$topOrders = [];
foreach($data as $value):
foreach($value as $order):
if(isset($$order)):
$$order++;
else:
$$order = 1;
endif;
$topOrders[$order] = $$order;
endforeach;
endforeach;
print_r($topOrders);
In $topOrders, you have an array that contains as key the ID, and as value you got the number of orders. All you have to do is to sort your array to get your top 3.
I want to select 5 random ID's from my array of rows. Here is my array $test:
$test = [
['id' => 13, 'pets' => 8],
['id' => 15, 'pets' => 8],
['id' => 16, 'pets' => 10],
['id' => 17, 'pets' => 9],
['id' => 18, 'pets' => 10],
['id' => 19, 'pets' => 10],
['id' => 20, 'pets' => 0],
['id' => 21, 'pets' => 8],
['id' => 22, 'pets' => 9],
['id' => 23, 'pets' => 4],
['id' => 24, 'pets' => 0],
['id' => 40, 'pets' => 8],
['id' => 43, 'pets' => 2],
];
How can I select 5 random ID's from the array and put them into a string like this:
$ids = '13,17,18,21,43';
I've tried to use array_rand(), but it does not seem to work for my type of array. I'm not sure if there are any other built in PHP functions that can do this type of job or if I have to create my own function. It would be nice to have my own function like this to plug in the number of required values.
You can use array_column to only get the ID's and shuffle them.
Then use array_slice to get five items and implode.
$id = array_column($arr, "id");
Shuffle($id);
Echo implode(",", array_slice($id, 0, 5));
First extract the id column indexing also by the id, then pick 5 random ones, and finally implode into a comma separated list. Since keys must be unique, this has the added benefit of not returning duplicate ids if there happen to be duplicates in the array:
$ids = implode(',', array_rand(array_column($test, 'id', 'id'), 5));
For a function:
function array_rand_multi($array, $key, $num) {
return implode(',', array_rand(array_column($array, $key, $key), $num));
}
If you want random, unique ids in a random order, I recommend shuffling the array, then isolating upto 5 subarrays, then extracting the id values, then joining with commas. This way array_column() doesn't need to iterate the full array.
Code: (Demo)
shuffle($test);
echo implode(
',',
array_column(
array_slice($test, -5),
'id'
)
);
If you want random, unique ids and don't mind that they will be in the same order as your input rows, then array_rand() can be used.
#AbraCadaver's approach works by applying temporary keys to the input array, picking five random keys, then joining with commas. Because the values inside the rows are never used, null can also be used as array_column()'s second parameter. These approaches should not be used if duplicate ids need to be honored. In other words, because id values are being applied to the first level keys, php will automatically destroy any rows with duplicated ids -- because a single level of an array cannot contain duplicate keys.
One way to avoid potentially destroying data is to call array_rand() on the original indexes of the input array, then filter those unique indexes by 5 randomly selected indexes. (Demo)
echo implode(
',',
array_column(
array_intersect_key(
$test,
array_flip(array_rand($test, 5))
),
'id'
)
);
Finally, if you want 5 randomly selected, randomly ordered ids which may be selected more than once, then just make 5 iterated calls of array_rand(). (Demo)
for ($x = 0, $delim = ''; $x < 5; ++$x, $delim = ',') {
echo $delim . $test[array_rand($test)]['id'];
}
Or (Demo)
echo implode(
',',
array_map(
fn() => $test[array_rand($test)]['id'],
range(1, 5)
)
);
You can proceed like this (short example) :
<?php
$items = array(
array("id" => 43, "pets" =>2),
array("id" => 40, "pets" =>8),
array("id" => 24, "pets" =>0),
array("id" => 23, "pets" =>4),
);
$ids = $items[array_rand($items)]["id"].",".$items[array_rand($items)]["id"].",".$items[array_rand($items)]["id"];
echo $ids;
// Output Example : 24, 40, 23
?>
It will choose a random key from the main array ($items), example : 3, and output the "id" :
$items[3]["id"]
for this example.
Here is a demo : http://sandbox.onlinephpfunctions.com/code/32787091e341cdf8e172d96b065b14b3ca834846
I have a problem I need to solve and I'm sure there is a way of doing this, I'm just not exactly sure "what to search for" and how to find it.
I was thinking of doing this either in Excel or I could maybe try to make a PHP script to do it.
So basically, I have a set of substances. Each pair of substances is either compatible or incompatible with another one. So what I have is a table with rows and columns where there is either 0 or 1, i.e. compatible/incompatible.
Now what I want to do is try to find groups of substances, where all substances in that group are compatible with each other. And the goal is to find as large group as possible, or ideally, find the largest, second largest etc. and sort them from largest to smallest (given there could be some limitation for the minimum number of elements in that group).
I hope it makes sense, the problem is that I'm not sure how to solve it, but I think this is something that should be relatively commonly done and so I doubt the only way is writing a script/macro from scratch that would use brute force to do this. This would also probably not be very efficient as I have a table with over 30 elements.
So just to make it more clear, for example here is a simplified table of what my data looks like:
Substance A B C D
A 0 1 1 1
B 1 0 0 1
C 1 0 0 0
D 1 0 0 0
If you use only php without database you can use uasort to sort by sum all elements of related array.
<?php
$substances = [
'A' => [
'A' => 0,
'B' => 1,
'C' => 1,
'D' => 0,
],
'B' => [
'A' => 1,
'B' => 0,
'C' => 1,
'D' => 1,
],
'C' => [
'A' => 0,
'B' => 1,
'C' => 0,
'D' => 0,
]
];
uasort ($substances, function ($a, $b) {
$a = array_sum($a);
$b = array_sum($b);
if ($a == $b) {
return 0;
}
return ($a > $b) ? -1 : 1;
});
var_export($substances);