I have an array with 950 elements. Values of elements are between 80-110. I want to count how many of them between 80-90, 90-100 and 100-110. Then I'll show them on a graph. Is this possible to count elements like that in php ?
You can simply do it by running a for loop. Create an array containing a range of elements and run a for loop. While you will run the loop on that time count the array element according to your given three groups. Finally you will get the total number of elements inside the given range. For your better help below I am giving an example :
<?php
$number = array(80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110);
$count1 = $count2 = $count3 = 0;
for ($i = 0; $i < sizeof($number); $i++) {
if($number[$i] >= 80 && $number[$i] <= 90 ) {
$count1++;
}
if($number[$i] >= 90 && $number[$i] <= 100 ) {
$count2++;
}
if($number[$i] >= 100 && $number[$i] <= 110 ) {
$count3++;
}
}
echo "The number between 80-90 = ".$count1."<br>";
echo "The number between 90-100 = ".$count2."<br>";
echo "The number between 100-110 = ".$count3."<br>";
?>
I think OP may have been going for a more pythonic answer (but in PHP)
//only valid in php 5.3 or higher
function countInRange($numbers,$lowest,$highest){
//bounds are included, for this example
return count(array_filter($numbers,function($number) use ($lowest,$highest){
return ($lowest<=$number && $number <=$highest);
}));
}
$numbers = [1,1,1,1,2,3,-5,-8,-9,10,11,12];
echo countInRange($numbers,1,3); // echoes 6
echo countInRange($numbers,-7,3); // echoes 7
echo countInRange($numbers,19,20); //echoes 0
the 'use' keyword indicates a 'closure' in php. Taken for granted in other languages, for example javascript, variables in an outer function are imported scope-wise into the inner function automatically (i.e. without special keywords), the inner function may also be called a "partial function".
For some reason in PHP 5.2x or lower, variables were not imported scope-wise automatically, and in PHP 5.3 or higher, the use keyword can overcome this. The syntax is very simple:
$functionHandle = function(<arguments>) use (<scope-imported variables>){
//...your code here...
}
To avoid having too many comparisons jump to next loop when you have determined where your number belongs.
function countOccurences( array $numbersArray ):array
{
$return = array('n80to90' => 0, 'n90to100' => 0, 'n100to110' => 0);
foreach( $numbersArray as $number ){
if( $number < 80 || $number > 110 )
continue;
if($number < 91){
$return['n80to90']++;
continue;
}
if($number < 101){
$return['n90to100']++;
continue;
}
$return['n100to110']++;
}
return $return;
}
I cannot imagine that your statistical graphic that would want to represent values on the cusp of two groups as belonging to both groups. For this reason, I'll offer a condition-less function-less technique which can be execute in a foreach loop.
Code: (Demo)
$sampleData = range(80, 110);
$result = array_fill_keys(['80s', '90s', '100s'], 0); // set 0 defaults for incrementation
foreach ($sampleData as $value) {
++$result[(10 * ((int)($value / 10) <=> 9) + 90) . 's'];
}
var_export($result);
Output:
array (
'80s' => 10,
'90s' => 10,
'100s' => 11,
)
My technique manufactures the appropriate keys from the values by:
Dividing by 10
Truncating the decimal (same effect as floor()
Making a 3-way comparison via the spaceship operator (returns -1, 0, or 1)
Multiplying the "-1|0|1" by 10
Adding 90
And finally concatenating an "s".
The ++ means add 1 to the current value of the element with the corresponding key.
Alternatively, if you wanted to group the values as "90+below" (80-90), "100+below" (91-100), and "110+below" (101-110), then it is a simple matter of adjusting the key-generating factors.
Instead of counting, I'll show the elements assigned to each group.
Code: (Demo)
$sampleData = range(80, 110);
foreach ($sampleData as $value) {
$result[(10 * ((int)(($value - 1) / 10) <=> 9) + 100) . '+under'][] = $value;
}
var_export($result);
Output:
array (
'90+under' =>
array (
0 => 80,
1 => 81,
2 => 82,
3 => 83,
4 => 84,
5 => 85,
6 => 86,
7 => 87,
8 => 88,
9 => 89,
10 => 90,
),
'100+under' =>
array (
0 => 91,
1 => 92,
2 => 93,
3 => 94,
4 => 95,
5 => 96,
6 => 97,
7 => 98,
8 => 99,
9 => 100,
),
'110+under' =>
array (
0 => 101,
1 => 102,
2 => 103,
3 => 104,
4 => 105,
5 => 106,
6 => 107,
7 => 108,
8 => 109,
9 => 110,
),
)
Related
So I try to send some output to a file but it doesn't look the way I want.
I have a file with some random integer values , it looks like this:
78, 60, 62, 68, 71, 68, 73, 85, 66, 64, 76, 63, 81, 76, 73, 68, 72, 73, 75, 65, 74, 63, 67, 65, 64, 68, 73, 75, 79, 73
4, 6, 9, 11, 16
9, 10, 16, 18, 20
1, 3, 8, 10, 15
Now what I want to do is for each of these lines print the average, max and min number into another file.
My code for this part is:
while (!feof($aFile))
{
$cnt += 1;
$anArray = array();
$anArray = explode(",", fgets($aFile));
foreach ($anArray as $value)
{
$value = (int)$value;
}
$line = "Line $cnt: Average=" . array_sum($anArray) / count($anArray) . " Max=" . max($anArray) . " Min=" . min($anArray);
fwrite($resultsFile, $line);
fwrite($resultsFile, "\n");
}
The problem is that the resultsFile looks like this:
Line 1: Average=70.6 Max= 85 Min= 60
Line 2: Average=9.2 Max= 16
Min=4
Line 3: Average=14.6 Max= 20
Min=9
Line 4: Average=7.4 Max= 15 Min=1
I couldn't exactly copy paste this , because when I do it pastes it the right way
The problem is that it changes line and then prints Min .
Total Average=50.533333333333 Total Max= 85 Total Min=1
This is kinda tricky and is a combination of several moments.
First - you have new line symbol (\n) in each string of your file.
So, for example string 9, 10, 16, 18, 20 is really a 9, 10, 16, 18, 20\n string.
When you explode this string by ,, last element of your $anArray is not 20, but 20\n (with new line).
Now you're trying to cast each element to int, but as by default php works with copy of array, your initial values in $anArray are unchaged. As correctly stated in comments - to apply changes to original array, use $&value notation:
foreach ($anArray as &$value)
{
$value = (int)$value;
}
And last - all following operations on your $anArray silenlty cast 20\n to int (20). But as noticed in max manual, for example:
The actual value returned will be of the original type with no conversion applied
So, in array ['9', '10', '16', '18', '20\n'] max value will be 20, but returned value will be 20\n which creates a new line and moves Min=... output to next line.
I have 2 arrays:
$arr_1=array(200, 300, 200, 200);
$arr_2=array(
1 => array(70, 90, 70, 20),
2 => array(115, 150, 115, 35),
3 => array(205, 250, 195, 55),
4 => array(325, 420, 325, 95),
5 => array(545, 700, 545, 155)
);
Now I need some way to get array keys for arrays in $arr_1 where all their values are less than all values from $arr_2.
In the above example it must return key 1 AND key 2 from $arr_2 without using a foreach loop.
You can use array_filter to filter the elements (it preserves keys) and then pass the result to array_keys to receive an array of keys.
Also, your condition can be spelled this way: "return subarrays from $arr_2 where highest value is smaller than smallest value of $arr_1."
$arr_1=array(200, 300, 200, 200);
$arr_2=array(
1 => array(70, 90, 70, 20),
2 => array(115, 150, 115, 35),
3 => array(205, 250, 195, 55),
4 => array(325, 420, 325, 95),
5 => array(545, 700, 545, 155)
);
$filtered = array_filter($arr_2, function($value) use ($arr_1) {
return max($value) < min($arr_1);
});
$keys = array_keys($filtered);
var_dump($keys);
If you are only interested in comparing the subarrays against the lowest value in $arr_1, then best practices dictates that you declare that value before entering the array_filter(). This will spare the function having to call min() on each iteration. (Demo)
$arr_1=[200,300,200,200];
$arr_2=[
1=>[70,90,70,20],
2=>[115,150,115,35],
3=>[205,250,195,55],
4=>[325,420,325,95],
5=>[545,700,545,155]
];
$limit=min($arr_1); // cache this value, so that min() isn't called on each iteration in array_filter()
$qualifying_keys=array_keys(array_filter($arr_2,function($a)use($limit){return max($a)<$limit;}));
var_export($qualifying_keys);
/*
array(
0=>1,
1=>2,
)
*/
Preamble:
This question is not about to learn PHP, nor is it code that I plan to use in a productive environment. I just want so see and learn better ways to do this work, as I did in my approach. So please only correct my code or show me better, faster or shorter solutions for doing it. The problem itself has already been solved. Thank you!
The Problem:
Some days ago a user asked a question here on SO. His problem has got my attention, because I wanted to find a way to solve his needs.
He wanted to get all possible key combinations of an PHP array, where the sum of the values is 100, or as close as possible to 100. He had given us an example array, which I will use for my examples too:
$array = array(25, 30, 50, 15, 20, 30);
For example, one result should be [2, 4, 5], because 50 + 20 + 30 is 100.
$sum = $array[2] + $array[4] + $array[5]; // = 100
I think the basic idea should be clear. Now let's take a look at my work ...
My Approach:
So this problem has got my attention as a developer. At first, I thought it would be pretty simple. Just do some addition and check the result. But then I've noticed that there some points to keep in mind ...
There are plenty of combinations to test. For the example array, there would be up to 720 (6! = 1*2*3*4*5*6 = 720) possible permutations. To get all possible combinations, I wanted to get all possible permutations of the array first.
But that was only the half truth. Because there could be double values in the array (as in your example the 30), we could not get all possible permutations of the array values, we had to get all possible permutations of the array keys instead.
So I've used the pc_permut function of the php cookbook and modified it for my needs. It will return all possible permutations in an array of keys.
/**
* gets all possible permutations of $array
* #param array $array
* #param array $permutations
* #return array
*/
function permutations($array, $permutations = array()) {
if( !empty($array) ) {
$result = array();
for( $i = count($array) - 1; $i >= 0; --$i ) {
$newItems = $array;
$newPerms = $permutations;
list($values) = array_splice($newItems, $i, 1);
array_unshift($newPerms, $values);
$result = array_merge($result, permutations($newItems, $newPerms));
}
}
else {
$result = array($permutations);
}
return $result;
}
The result of this function is a multidimensional array, containing all permutations in an ordered key array.
Array (
[0] => Array (
[0] => 0
[1] => 1
[2] => 2
[3] => 3
[4] => 4
[5] => 5
)
[1] => Array (
[0] => 1
[1] => 0
[2] => 2
[3] => 3
[4] => 4
[5] => 5
)
[...
)
So, for now I got all permutations to work with. The calculation of the possible combinations is not so hard at all. I'll just loop through the permutation, increment the sum until they've reached 100 or above and return the key combination.
But I find out that I missed one thing. As I got all possible permutations, there are even some results doubled in the list. To explain, this two results are basically the same:
[2, 4, 5]; // 50 + 20 + 30 = 100
[4, 5, 2]; // 20 + 30 + 50 = 100
I've ended up sorting the keys after calculation and use them as index in the resulting array. So it would be sure, that every combination only exists once in the result. This is my combinations function:
/**
* gets all possible key combinations of $array with a sum below or equal $maxSum
* #param array $array
* #param integer $maxSum
* #return array
*/
function combinations($array, $maxSum) {
// get all permutations of the array keys
$permutations = permutations(array_keys($array));
$combinations = array();
// loop all permutations
foreach( $permutations as $keys ) {
// create a container for each permutation to store calculation
$current = array(
"sum" => 0,
"keys" => array()
);
// now loop through the permutation keys
foreach( $keys as $key ) {
// if the addition is still between or equal $maxSum
if( $current["sum"] + $array[$key] <= $maxSum ) {
// increment the sum and add key to result
$current["sum"] += $array[$key];
$current["keys"][] = $key;
}
}
// to be sure each combination only exists once in the result
// order the keys and use them as array index
sort($current["keys"]);
$combinations[join("", $current["keys"])] = $current;
}
// remove the created key-index from array when finished
return array_values($combinations);
}
The execution is simple straight forward:
$array = array(25, 30, 50, 15, 20, 30);
print_r(combinations($array, 100));
The result is an array, containing all combinations. For our example array there are eleven possible combinations. The result looks like this:
Array (
[0] => Array (
[sum] => 90
[keys] => Array (
[0] => 0
[1] => 1
[2] => 3
[3] => 4
)
)
[1] => Array (
[sum] => 90
[keys] => Array (
[0] => 0
[1] => 2
[2] => 3
)
)
[...
Since I've written this script as an answer of the original question, I'll ask myself, if there would be another, even better way to do the work. Maybe there is a way without permutations, or a way to exclude same combinations from calculation or the resulting array. I know that I could execute the calculation directly in the permutations function too, but this would be basically the same work flow.
I would really like to get some advises, tips or improvements from you. I think here is some potential to improve the script, but I have actually no idea how to. But I'm sure it could be done more simple and straight forward ...
Thanks for your time! :)
Sorting the array leads to some possibilities: here is the one i'm thinking of:
I denote a(selectedIndexes) the element composed of all elements of selectedIndexes e.g.
a({25, 30, 30}) = (25, 30, 30)
P(n) is the set of all combinations of the indexes 1 to n and for clarity sake my arrays starts at index 1 (thus P(2)={1, 2, (1, 2)})
I'm using 2 break conditions explained in the pseudocode below. 1st one is the first element of aSorted = the allowed sum.
2nd one is the sum is too small compared to aSorted first element
selectedIndexes = {}
sum = 100
aSorted = {15, 20, 25, 30, 30, 50} //starting values for the example
//to clarify the following function
aSum = {15, 35, 60, 90}
function n(aSorted, sum, selectedIndexes){
compute aSum //precisely search in aSum for the index at which
//the elements are bigger than sum, and cut
answer = (P(count(aSum))) X a(selectedIndexes) // with X being the cartesian product
for (i=count(aSum)+1; i<=count(aSorted); i++){
newASorted = splice(aSorted, count(aSum))
// 1st break condition
if(newASorted is empty) return answer
// 2nd break condition the new sum < the first element of aSorted
if (aSorted(i)<sum && sum-aSorted(i)>=aSorted(1)){
answer += n(newASorted, sum-aSorted(i), push(selectedIndexes,
i))
}
}
return answer
}
The complexity of this algorithm feels quadratic(after a quick check it's more like of order n^log2(n)) in regard to the number of elements in the array
To make it less abstract, let's develop the example(warning i trust the example more than the pseudocode although i didn't see inaccuracies myself in the pseudocode):
n({15, 20, 25, 30, 30, 50}, 100, {}) = P(4) + n({15, 20, 25, 30, 30}, 50, {6}) + n({15, 20, 25, 30}, 70, {5})
Starting by developing the first n function of the right side of the equation
n({15, 20, 25, 30, 30}, 50, {5}) = (P(2) X {6}) + n({15, 20, 25, 30}, 20, {5, 6}) + n({15, 20, 25}, 20, {4, 6}) + n({15, 20}, 25, {3, 6})
n({15, 20, 25, 30}, 20, {5, 6}) = (P(1) X {(5, 6)}) // + n({15}, 0, {2, 5, 6}) (but 0<15) break condition 2
n({15, 20, 25}, 20, {4, 6}) = P(1) X {(4, 6)} // and break condition 2
n({15, 20}, 25, {3, 6}) = P(1) X {(3, 6)} // + n({15}, 5, {2, 3, 6}) (but 5<15) break condition 2
Now developing the second n function of the right side of the equation
n({15, 20, 25, 30}, 70, {5}) = (P(3) X {5}) + n({15, 20, 25}, 40, {4, 5})
n({15, 20, 25}, 40, {4, 5}) = (P(2) X {(4, 5)}) + n({15, 20}, 15, {3, 4, 5})
n({15, 20}, 15, {3, 4, 5}) = P(1) x {(3, 4, 5)} // + n({}, 0, {1, 3, 4, 5}) new break condition aSum is empty
I want to grant bonuses to my players based on how many friends they have.
I have breakpoints (for example 0, 1, 5, 10, 25)
For 0 friends he gets 0 bonus.
For 1 friend he gets 1000, for 5 or above 2000 etc...
What I do right now is this:
public function getFriendsBonusByFriendsAmount($amount)
{
switch (true) {
case ($amount < 1):
return 0;
case ($amount < 5):
return 1000;
case ($amount < 10):
return 2000;
case ($amount < 25):
return 3000;
case ($amount >= 25):
return 5000;
}
}
I'm looking for a different way to find the bonus without foreach/switch
Perhaps think of an array or arrays that I could play with?
$bonusBreakpoints = [
0 => 0,
1 => 1000,
5 => 2000,
10 => 3000,
25 => 5000
]
Or perhaps two arrays with respective indexs?
I thought of a way to do it but it's a memory waste:
$bonusPerFriends = [
0 => 0,
1 => 1000,
2 => 1000,
3 => 1000,
4 => 1000,
5 => 2000,
6 => 2000,
...
25 => 5000
]
I rather not to use that way.
Well, sometimes foreach/switch will be the best solution :)
/**
* Calculates bonus based on how many
* friends the player have from predefined breakpoints
**/
function getBonus($friends) {
$bonuses = [0, 1000, 2000, 3000, 5000];
$stops = [[PHP_INT_MIN, 0], [1, 4], [5, 14], [15, 24], [25, PHP_INT_MAX]];
// replace the stops by bonus if match, otherwise return empty - O(n)
$bonus = array_map(function ($stop, $bonus) use ($friends) {
if ($friends >= $stop[0] && $friends <= $stop[1]) {
return $bonus;
}
}, $stops, $bonuses);
// clean up the array from empty values - O(n)
$bonus = array_filter($bonus , 'is_numeric');
// from array(3 => 3000) to 3000 - O(1)
return array_pop($bonus);
}
results:
echo getBonus(0).PHP_EOL; // 0
echo getBonus(4).PHP_EOL; // 1000
echo getBonus(12).PHP_EOL; // 2000
echo getBonus(20).PHP_EOL; // 3000
echo getBonus(39).PHP_EOL; // 5000
P.S. $bonuses and $stops here must be equal length.
#andrey-mischenko 's answer is technically right, but does not solve the problem without foreach, as you said in your question. (Edit: answer was removed) Try this:
$bonusBreakpoints = [
0 => 0,
1 => 1000,
5 => 2000,
10 => 3000,
25 => 5000
];
$justTheKeys = array_keys($bonusBreakpoints);
public function getFriendsBonusByFriendsAmount($amount)
{
$bonus = array_reduce($justTheKeys, function($carryOver, $item) use ($amount)
{
if ($amount >= $item) return $bonusBreakpoints($item);
return $carryOver;
}
return $bonus;
}
(I'm aware that this is not what array_reduce is originally intended for. I understood the question as a mind-game. Like "find creative ways to solve this problem apart from the obvious ones, like loops or switches." If I had to code this for work, I'd probabely use a loop, too. :) )
After reading the answer and more research I concludes that binary search is my best option.
The data should look somewhat like this:
$bonuses = [
[ 'min' => 0, 'max' => 0, 'amount' => 0 ]
[ 'min' => 1, 'max' => 4, 'amount' => 1000 ]
[ 'min' => 5, 'max' => 14, 'amount' => 2000 ]
...
[ 'min' => 25, 'max' => PHP_INT_MAX, 'amount' => 5000 ]
]
You start at the count($bonuses)/2 and from there check whether you are below the min, is so goes halfway, otherwise check if you are above max, and go halfway there. else... just return the bonus coz you are between the right range.
Since most of my users don't have any friends or more than 25 I would probably check the first and last cell first.
I've searched for this but can't seem to find the exact answer. I want to use array_multisort to simultanously sort 3 arrays based on numeric values in 3 of the arrays. Basically I want to make a "standings" table similar to what you'd see for NFL/NHL standings etc. I have 3 arrays, tempIDs (string), tempWins (numeric), tempWinPercentage (numeric). I need all 3 to be sorted at the same time based first on wins, and then if there is a tie, win percentage.
I can't seem to get array_multisort to work with more than just 2 arrays, so maybe I'm misunderstanding the terminology when they say that it can work with "several" arrays. Thank you!
You should have a data array like this:
$data = array(
0 => array(
'tempIDs' => 'something',
'tempWins' => 10,
'tempWinPercentage' => 50,
),
1 => array(
'tempIDs' => 'something else',
'tempWins' => 10,
'tempWinPercentage' => 60,
),
3 => array(
'tempIDs' => 'something more',
'tempWins' => 20,
'tempWinPercentage' => 50,
),
);
Then sort this array using usort($data, 'my_sort_cb')
Your callback method should first compare tempWins, and if they are equal, compare tempWinPercentages:
function my_sort_cb($a, $b) {
if ($a['tempWins'] > $b['tempWins']) return 1;
if ($a['tempWins'] < $b['tempWins']) return -1;
if ($a['tempWinPercentage'] > $b['tempWinPercentage']) return 1;
if ($a['tempWinPercentage'] < $b['tempWinPercentage']) return -1;
return 0;
}
(this can be made shorter)
I can't seem to get array_multisort to
work with more than just 2 arrays, so
maybe I'm misunderstanding the
terminology when they say that it can
work with "several" arrays. Thank you!
I think they mean it can be used for sorting more than two arrays, but the other arrays will be sorted basing on the first one.
In example, executing this code
$a1 = array(12, 23, 34, 45, 45, 34);
$a2 = array(234, 56, 243, 456, 34, 346);
$a3 = array(654, 56, 8, 12, 56, 90);
array_multisort($a1, $a2, $a3);
you will get the arrays sorted as if the would have been defined as
$a1 = array(12, 23, 34, 34, 45, 45);
$a3 = array(654, 56, 8, 90, 56, 12);