Divide a two dimensional array into surfaces - php

For a project it's required to arrange a two dimensional array into planes that are in proportion to each other based on a the percentage of each plane. (I hope this make sense, else see example below). In this 2D array the 'first' level represents the rows and the 'second' level, the columns. For example;
array(
// row 1
array(
// items
number1
number2
numberN
),
// row 2
array(
// items..
),
// row N
array(
// items..
)
)
The numbers in this array has te be added/arranged in such a way that they form panels. The panels together form one grid. Each digit is representing a item (doesn't matter for the question what this is). I came up with a solution myself. Click here for print of the 2D array (The groups are color coded.).
Lets say, there are three groups (listed below). The groups do represent the panels, introduced above. Each group has some percentage between zero and hundred. The sum of the percentage of the planes is required to be hundred percent. The maximum amount of group is seven. Example group info;
Group 1 (Panel A): 70%
Group 2 (Panel B): 20%
Group 3 (Panel C): 10%
Again this arrangement should result in one large panel with (sub)panels in it. As shown in this schematic figure.
I came up whit the idea to divide the end result into 4 corners. Each corner will be calculated by the rules. These corners should than be mirror (horizontally and/or vertically) based on what corner it is (Upper left, Upper right, Lower left, Lower right).
List of rules;
The number of items should be te same for each row
The aspects ratio of the complete grid should be 2 to 1. So the width is two times the hight.
The amount of rows are based on the total items, since the aspect is known.
After some days of work I was able to come up with a working script. But this does act weird (weird as in, not as expected) in some cases. See current solution above.
So, my question is; How do lever designers this? Is this a known problem and are there solution (like algorithm) what solve this (kind of) questions? I am struggling with the following problem for a long time now. Searching on the internet, trying to find similar problems. But I did not succeed.
I am not asking for a ready made solution. Just a pointer in the right direction would be much appreciated.

Assuming the planes should have approximately the same "aspect ratio" as the complete matrix, you could use this algorithm:
Calculate for each percentage what would be the coefficient to apply to the width and height to get the exact area that would be left over after subtracting the percentage of the available area. This coefficient is the square root of the coefficient that needs to be applied to the area (related to the percentage).
As this coefficient will in general be a non-integer number, check which way of rounding the width and height yields an area that comes closest to the desired area.
Repeat this for each plane.
Here is the code:
function createPlanes($width, $height, $groupPercentages) {
$side = 0;
$area = $width * $height;
$planeWidth = $width;
$planeHeight = $height;
$sumPct = 0;
$coefficient2 = 1;
foreach ($groupPercentages as $i => $pct) {
$plane = [
"column" => floor(($width - $planeWidth) / 2),
"row" => floor(($height - $planeHeight) / 2),
"width" => $planeWidth,
"height" => $planeHeight,
];
$coefficient2 -= $pct / 100;
$coefficient = sqrt($coefficient2);
$planeArea = $coefficient2 * $area;
$planeWidth = $coefficient * $width;
$planeHeight = $coefficient * $height;
// determine all possible combinations of rounding:
$deltas = [
abs(floor($planeWidth) * floor($planeHeight) - $planeArea),
abs(floor($planeWidth) * min(ceil($planeHeight), $plane["height"]) - $planeArea),
abs(min(ceil($planeWidth), $plane["width"]) * floor($planeHeight) - $planeArea),
abs(min(ceil($planeWidth), $plane["width"]) * min(ceil($planeHeight), $plane["height"]) - $planeArea)
];
// Choose the one that brings the area closest to the required area
$choice = array_search(min($deltas), $deltas);
$planeWidth = $choice & 2 ? ceil($planeWidth) : floor($planeWidth);
$planeHeight = $choice & 1 ? ceil($planeHeight) : floor($planeHeight);
$newSumPct = ($area - $planeWidth * $planeHeight) / $area * 100;
$plane["pct"] = $newSumPct - $sumPct;
$sumPct = $newSumPct;
$planes[] = $plane;
}
return $planes;
}
// Example call for a 2D array with 20 columns and 32 rows, and
// three percentages: 10%, 20%, 70%:
$planes = createPlanes(20, 32, [10, 20, 70]);
The $planes variable will get this content:
array (
array (
'column' => 0,
'row' => 0,
'width' => 20,
'height' => 32,
'pct' => 10.9375,
),
array (
'column' => 0,
'row' => 1,
'width' => 19,
'height' => 30,
'pct' => 20,
),
array (
'column' => 1,
'row' => 3,
'width' => 17,
'height' => 26,
'pct' => 69.0625,
),
)
The inner attributes define where the plane starts (row, column), and how large it is (height, width), and which is the actual percentage that plane is in relation to the total area.
Note that the actual 2D does not need to be part of the algorithm, as its values don't influence it.

Related

Looping by using paths with condition

I am creating a hierarchy based referral system. In which i am storing the parent paths in a column named "path", so that i can easily find parents in a single query ( i am not using recursive ).
In this each user will be having maximum of 5 referrals alone. My problem is that , i am having a commission system which splits up the new users money into various commissions for each level. For example the direct sponsor of new user will get a commission of 200 rs, and direct sponsor of previous sponsor will get 100 and it goes on for 10 levels.
So in the path, i can get "id" of all parents and their corresponding parents. Path example /1/2/3/4/44/ - so from this path we can understand that 44 is the direct parent, and 4 is the parent of 44 and 3 is the parent of 4 and 2 is the parent of 3 and 1 is the parent of 2.
So my problem is , my commission are splits like this, level 1 - 200, level 2 - 100, level 3 - 50, level 4 - 30, level 5 - 10 etc.
How to loop this commissions to those parents ? I seriously couldn't get the idea, so i haven't wrote any code. Please kindly help.
How to update various commissions to users by which we found using paths ?
As the comment mentioned, you can split the string into an array using explode. With that, you can iterate over it and compute the commission.
http://sandbox.onlinephpfunctions.com/code/4b4d1232a96b58e95abe77a645accd36b2a98af3
<?php
$input = '/1/2/3/4/44/';
// Split your string into an array and remove empty values
$levels = array_filter(explode('/', $input));
// Make the array key start at 1 instead of 0.
$levels = array_combine(range(1, count($levels)), $levels);
// Define your levels and pricing
$levelPricing = [
'1' => 200,
'2' => 100,
'3' => 50,
'4' => 30,
'5' => 10,
];
$sum = 0;
foreach ($levels as $level => $parent)
{
$sum += $levelPricing[$level];
}
echo 'You get ₹' . $sum;
// Output: You get ₹390
Slightly condensed code which I find easier to read.
<?php
$input = '/1/2/3/4/44/';
// Split your string into an array, removes empty values, and then reoders the keys to start from 0.
$levels = array_values(array_filter(explode('/', $input)));
// Define your levels and pricing
$pricing = [
200,
100,
50,
30,
10
];
echo 'You get ₹' . array_sum(array_intersect_key($pricing, $levels));
// Output: You get ₹390

How to distribute values across an array according to percentages

I'm making a DBZ Xenoverse 2 stat distribution guide according to build so you receive consistent gameplay in your attributes. The thought is that if by level 80 you have a total of 332 stat/attribute points to distribute, and you want to keep a consistent point allocation while creating your build, you would need to gather the percentages of the attributes from the total points gathered and assign them according to those percentages for each level. So if I wanted a character build that looked like:
Health - 30
Ki - 42
Stamina - 42
Basic Attacks - 93
Strike Supers - 0
Ki Blast Supers - 125
Total - 332
The percentages would look like:
Health - 9.03614457831325%
Ki - 12.65060240963855%
Stamina - 12.65060240963855%
Basic Attacks - 28.01204819277108%
Strike Supers - 0%
Ki Blast Supers -37.65060240963855%
So on level 2 (since you don't get stats for level one) you get two stat points and your Attributes would look like this:
Health - 0
Ki - 0
Stamina - 0
Basic Attacks - 1
Strike Supers - 0
Ki Blast Supers - 1
Whereas on level 20 your stats would look like this:
Health - 5
Ki - 7
Stamina - 7
Basic Attacks - 16
Strike Supers - 0
Ki Blast Supers - 22
Total - 57
So the end result would look like:
LVL 1
stat array
LVL 2
stat array
...
LVL 80
stat array
Since the character receives a variable number of stats per level we have to have a hardcoded array and change our distribution based on that, along with what has already been used and what should be used.
<?php
class Stats {
public function show_level_proportions( $build_stats ) {
$build_stat_labels = [ 'max_health', 'max_ki', 'max_stamina', 'basic_attack', 'strike_supers', 'ki_blast_supers' ];
$build = array_combine($build_stat_labels, $build_stats);
$stat_percents = $this->calculate_stat_percents($build);
$get_stats_per_lvl = $this->get_stats_per_lvl($stat_percents, $build);
return $get_stats_per_lvl;
}
//Stats given from levels 1-20
private $incremental_points = [
0, //1
2, //2
3, //3
3, //4
3, //5
3, //6
3, //7
3, //8
3, //9
3, //10
3, //11
3, //12
3, //13
3, //14
3, //15
3, //16
3, //17
3, //18
3, //19
4, //20 total: 57
];
private function calculate_stat_percents( $build_stats ) {
$stat_sum = array_sum( $build_stats );
foreach ( $build_stats as $key => $value ) {
$calculated_stat = $this->calc_percents($stat_sum, $value);
$stat_percentages[$key] = $calculated_stat;
}
return $stat_percentages;
}
private function calc_percents($sum, $stat){
$product = ( $stat / $sum );
return round( $product, 8, PHP_ROUND_HALF_UP );
}
/*================
* This is the problem area
* I can get the percentages for the inputted stats,
* but I don't even know how to start getting and
* distributing the percentages for each level. So
* right now, it's static, only works with the input,
* and doesn't incorporate the $incremental_stats.
================*/
private function get_stats_per_lvl($percentages, $stats){
$stats_total = array_sum($this->incremental_points);
foreach( $percentages as $key => $value ){
$lvl_twenty_stats[$key] = $stats_total * $value;
$rounded_lvl_twenty_stats[$key] = round( $lvl_twenty_stats[$key], 0, PHP_ROUND_HALF_UP );
}
return $rounded_lvl_twenty_stats;
}
}
$stat_tracker = new Stats();
print_r( $stat_tracker->show_level_proportions([5, 0, 5, 20, 7, 20]) );
Okay, so, to answer this, I'll first go over the theory, then the actual implementation in PHP.
Theory
So, for any given moment, you have your current attribute values, and your ideal attribute values.
For both collections of attributes, you can compute the percentage of the total each attribute represents.
So, for example, given an attribute collection like:
{
"foo": 2,
"baz": 1,
"bar": 3,
"foobar": 0
}
The total points here is 6, so the percentage of the total for each as computed would be (in pseudo-JSON):
{
"foo": 33.3%,
"baz": 16.6%,
"bar": 50.0%,
"foobar": 0.0%
}
If you compute this percentage list for each attribute collection (the desired and the current), you can then subtract each value of the desired from the current to get the percentage offset that each current value is off by.
So, a negative offset percentage indicates that the given stat that is associated with that offset is below where it should be, a 0 offset means it is exactly distributed evenly for that level, and a positive offset percentage indicates that the offset is above where it should be.
Since we can't distribute parts of a point, there will always be added points that knock it too high, so we can safely ignore the attributes that have a positive percentage offset.
Similarly, attributes with exactly 0 as their percentage offset can also be ignored, as at least at the current moment, that attribute is correctly distributed.
So, we're only really concerned with attributes that have a negative offset percentage, as those are the ones that need to be incremented.
To wrap it all together, for each point assigned, we need to compute the percentage offsets of the current attributes, assign a point to the attribute with the lowest offset percentage, and repeat the process until we are out of points, recording where, and how many, points we assigned in the process.
Example
Let's use the same example as in OP's question. The ideal distribution attributes collection is (attribute names truncated):
{
basic: 93,
ss: 0,
kbs: 125,
health: 30,
ki: 42,
stamina: 42
}
And the current is a collection of zeroes (because no points have been assigned yet):
{
basic: 0,
ss: 0,
kbs: 0,
health: 0,
ki: 0,
stamina: 0
}
And we have 2 points to assign.
Point 1
For the first point, we compute the percentage offsets, which looks like:
{
basic: -0.28012048192771083,
ss: 0,
kbs: -0.37650602409638556,
health: -0.09036144578313253,
ki: -0.12650602409638553,
stamina: -0.12650602409638553
}
If we ignore all zeroes/positive values (as described before), and sort the negatives by the lowest negative, we get this result:
[
{ name: 'kbs', value: -0.37650602409638556 },
{ name: 'basic', value: -0.28012048192771083 },
{ name: 'ki', value: -0.12650602409638553 },
{ name: 'stamina', value: -0.12650602409638553 },
{ name: 'health', value: -0.09036144578313253 }
]
With kbs (Ki Blast Supers) as the lowest offset, so, we assign one point to that, and then move on to the next point.
Point 2
Again, let's compute the percentage offsets, with the increased KBS value. Note that KBS is now positive due to the increase.
{
basic: -0.28012048192771083,
ss: 0,
kbs: 0.6234939759036144,
health: -0.09036144578313253,
ki: -0.12650602409638553,
stamina: -0.12650602409638553
}
And then, again, sort by lowest negative, throwing out zeroes and positive values. Note that KBS is now not eligible because it is slightly too high compared to the other values.
[
{ name: 'basic', value: -0.28012048192771083 },
{ name: 'ki', value: -0.12650602409638553 },
{ name: 'stamina', value: -0.12650602409638553 },
{ name: 'health', value: -0.09036144578313253 }
]
So now basic (Basic Attacks) has the lowest negative, and so we assign the final point to it.
So, our total points assigned looks like:
{
kbs: 1,
basic: 1
}
Which is correctly assigned based on OP's expected results.
Implementation
As for implementation in PHP, I won't give a 100% working example, but the basic idea is you want a function called something like getPointsDistributionForTarget which takes the current attribute scores, the target attribute score distributions, and the amount of points to assign.
function getPointsDistributionForTarget(current, target, pointsToSpend) { ... }
Within that function, you'll want to loop the amount of times there is points to spend, doing the following each loop:
Computing the sorted array of offset percentages
Take the top (0th index) value and:
Increasing the associated current attribute value and increasing it by one, and
Note down that you increased that attribute by one, in a running list of totals per attribute
Hopefully this helps! Let me know if you have any issues. I have a working implementation in Node (Javascript) I can provide that I used to test this theory and produce the numbers in the example, or I can try to convert that example to PHP if it helps.

Filter an array based on density

I have a sample graph like one below.., which I plotted with set of (x,y) values in an array X.
http://bubblebird.com/images/t.png
As you can see the image has dense peak values between 4000 to 5100
My exact question is can I programmatically find this range where the graph is most dense?
ie.. with Array X how can I find range within which this graph is dense?
for this array it would be 4000 - 5100.
Assume that the array has only one dense region for simplicity.
Thankful if you can suggest a pseudocode/code.
You can use the variance of the signal on a moving window.
Here is an example (see the graph attached where the test signal is red, the windowed variance is green and the filtered signal is blue) :
:
test signal generation :
import numpy as np
X = np.arange(200) - 100.
Y = (np.exp(-(X/10)**2) + np.exp(-((np.abs(X)-50.)/2)**2)/3.) * np.cos(X * 10.)
compute moving window variance :
window_length = 30 # number of point for the window
variance = np.array([np.var(Y[i-window_length / 2.: i+window_length/2.]) for i in range(200)])
get the indices where the variance is high (here I choose the criterion variance superior to half of the maximum variance... you can adapt it to your case) :
idx = np.where(variance > 0.5 * np.max(variance))
X_min = np.min(X[idx])
# -14.0
X_max = np.max(X[idx])
# 15.0
or filter the signal (set to zero the points with low variance)
Y_modified = np.where(variance > 0.5 * np.max(variance), Y, 0)
you may calculate the absolute difference between the adjacent values, then maybe smooth things a little with sliding window and then find the regions, where the smoothed absolute difference values are at 50% of maximum value.
using python (you have python in tags) this would look like this:
a = ( 10, 11, 9, 10, 18, 5, 20, 6, 15, 10, 9, 11 )
diffs = [abs(i[0]-i[1]) for i in zip(a,a[1:])]
# [1, 2, 1, 8, 13, 15, 14, 9, 5, 1, 2]
maximum = max(diffs)
# 15
result = [i>maximum/2 for i in diffs]
# [False, False, False, True, True, True, True, True, False, False, False]
You could use classification algorithm (for example k-means), to split data into clusters and find the most weighted cluster

Rounding in PHP to achieve 100%

I need to total the number of clicks over 10 links on my page and then figure out the percentage of people that clicked each. This is easy division, but how do I make sure that I get a round 100% at the end.
I want to use the below code, but am worried that a situation could arise where the percentages do not tally to 100% as this function simply removes the numbers after the period.
function percent($num_amount, $num_total) {
$count1 = $num_amount / $num_total;
$count2 = $count1 * 100;
$count = number_format($count2, 0);
echo $count;
}
Any advice would be much appreciated.
Instead of calculating one percentage in your function you could pass all your results as an array and process it as a whole. After calculating all the percentages and rounding them make a check to see if they total 100. If not, then adjust the largest value to force them all to total 100. Adjusting the largest value will make sure your results are skewed as little as possible.
The array in my example would total 100.02 before making the adjustment.
function percent(array $numbers)
{
$result = array();
$total = array_sum($numbers);
foreach($numbers as $key => $number){
$result[$key] = round(($number/$total) * 100, 2);
}
$sum = array_sum($result);//This is 100.02 with my example array.
if(100 !== $sum){
$maxKeys = array_keys($result, max($result));
$result[$maxKeys[0]] = 100 - ($sum - max($result));
}
return $result;
}
$numbers = array(10.2, 22.36, 50.10, 27.9, 95.67, 3.71, 9.733, 4.6, 33.33, 33.33);
$percentages = percent($numbers);
var_dump($percentages);
var_dump(array_sum($percentages));
Output:-
array (size=10)
0 => float 3.51
1 => float 7.69
2 => float 17.22
3 => float 9.59
4 => float 32.86
5 => float 1.28
6 => float 3.35
7 => float 1.58
8 => float 11.46
9 => float 11.46
float 100
This will also work with an associative array as the function parameter. The keys will be preserved.
These figures could now be presented in a table, graph or chart and will always give you a total of 100%;
What you want to do is this.
Total the number of clicks across the board, then divide each number by the total.
For example:
1134
5391
2374
2887
In this case, four buttons, with a total of 11786 clicks, so:
1134 / 11786 = 0.09621....
5391 / 11786 = 0.45740....
2374 / 11786 = 0.20142....
2887 / 11786 = 0.24495....
Then for each division, round the result to 'two decimal points', so the first result:
0.09621.... becomes 0.10
because the 3rd point is 5 or above, it would remain at 0.09 if the 3rd point was below 5.
Once you have all of the results rounded, multiply each by 100 then add them up.
The ending result will always be 100.
Should warn you however that depending on how you use each individual percentage, when you round them, any result less that 0.05 will become 0%, unless you keep the value before you round it so you can declare it as a percentage less than 1.
I think you want to use ceil() or round() .
Since these are floating point numbers, there is room for error. Be careful how you round, and be sure that you don't independently calculate the last remaining percentages. Simply subtract the total of what you have from 1 or 100.
Make sure you dont calculate separate sides of the equation, sum one side, then subtract the other from 1 or 100 or however you are handling your percentages.
I run into this quite a bit and have a hack for it.
$percentages = array(
'1' => 87.5,
'2' => 12.5,
'3' => 0,
'4' => 0,
'5' => 0
);
If you round those percentages for output, you will end up with 88% and 13% (101%)
round($percentages['1']);
round($percentages['2']);
// 88
// 13
So here is the code I use to fix it.
$checkTotal = array_sum($percentages);
$max = max(array_keys($percentages));
if ($checkTotal > 100) {
$percentages[$max] = $percentages[$max] - 1;
}
if ($checkTotal < 100) {
$percentages[$max] = $percentages[$max] + 1;
}
If it is 100, do nothing.
If it is less than 100, add 1 to equal 100
If it is over 100, subtract 1 to equal 100

How to pick random numbers from pot with some winning numbers?

i am trying to make a very simple game where i have 7 positions which are all hidden and within those there a 3 winning positions. I can pick randomly 3 times. I need to display whether the pick is a winning or not after every pick and store the result in a base.
Currently my thought where to generate an array of winning numbers on the first pick and then pick random number and check if it is in the winning array.
But i have a feeling that there is much more efficient way to do so.
Would appreciate if you would use PHP for coding examples, but pseudo code will do as-well.
EDIT
i am looking for the way to solve this without populating array with winning positions. maybe there is a way to do this with weights or probability percents.
Something like on first pick i have 3/7*100 percent chance to win. save result to base.
on second pick i have either 3/6*100 or 2/6*100 percent chance to win based weather i won in previous pick which i get from base.
Revised answer: this example does not require you to store the complete state of the game in a variable; instead, you just need to store the try count and won count:
$won = 0;
for($try = 0; $try < 3; $try++) {
$slots = array_fill(0, 7 - $try, 0); // blank slots
$lucky = array_fill(0, 3 - $won, 1); // lucky slots
$pot = array_replace($slots, $lucky); // make some of the slots lucky
$win = $pot[array_rand($pot)]; // randomly pick a slot
$won += $win == 1; // update won count
echo sprintf("Try %d: win=%d, total wins=%d\n", $try + 1, $win, $won);
}
Original answer:
$pot = array( // pot is (an associative) array; 0 = blank, 1 = win
"pos_1" => 0,
"pos_2" => 0,
"pos_3" => 0,
"pos_4" => 0,
"pos_5" => 0,
"pos_6" => 0,
"pos_7" => 0
);
$win = array_rand($pot, 3); // picks three indexes from the pot randomly
foreach($win as $w) {
$pot[$w] = 1; // set winning indicator
}
print_r($pot);
Output: array containing state of the pots.
Array
(
[pos_1] => 0
[pos_2] => 1
[pos_3] => 0
[pos_4] => 1
[pos_5] => 1
[pos_6] => 0
[pos_7] => 0
)
You can just save the positions of the winning numbers. This way you can always check their values using the [] operator for arrays. After all, you just pick the positions and not the numbers.
Update:
This way you even don't need to hide numbers. It's quite possible to have some more abstract "winning things" - characters, words, structures. However, it is important that you do not alter your array of hidden "things" in any way or at least update the stored winning positions accordingly if they stay the same. If that's not the case you'd naturally need to update the saved winning positions.
<?php
$arr = array(true, true, false, false, false, false, false);
shuffle($arr);
function pick($arr, $index) {
return isset($arr[$index]) && $arr[$index] === true;
}
var_dump($arr);
var_dump(pick($arr, 3));
var_dump(pick($arr, 5));
var_dump(pick($arr, 1));

Categories