php arrays sorting contained values - php

Ok, first of all, I'm not even sure the title is right, if so, I'm sorry.
I have this loop here which is the result of a MongoDB query:
foreach($cursor as $obj) {
$monster = $obj["type"];
$strenght = $obj["strenght"];
$obj["value"] = rand(5, 15);
}
now, I have put rand there to signify that value changes for each iteration. Now i want that this array, when is printed, is ordered by that $obj["value"], and be able to chose if ascending or descending.
ok, I have tried this
foreach($cursor as $obj) {
$type = $obj["monster"];
$strenght = $obj["strenght"];
$obj["value"] = rand(5, 15);
$newarr[] = $obj;
}
usort($newarr, "cmp");
function cmp($a, $b)
{ return $b['value'] < $a['value']; }
foreach ($newarr as $obj)
{
echo $obj['value'] . $obj['type'] . "<br/>";
}
As I expected, the
$obj["value"] = rand(5, 15);
does not get lost at every iteration in fact, the $newarr contains that value, the problem is that it does not sort them at all. The items are printed in the same order as they were put inside the array. Any help?
Thanks

function mysort ($arr,$d='asc') {
global $dir;
$dir = $d;
uasort($arr, 'cmp');
return ($arr);
}
function cmp ($a, $b) {
global $dir;
if($a['value'] == $b['value']) return 0;
else {
if(strtolower($dir) == 'asc')
return ($a['value'] > $b['value']) ? 1 : -1;
else if(strtolower($dir) == 'disc')
return ($a['value'] > $b['value']) ? -1 : 1;
}
}
print_r(mysort($obj, 'disc'));
ACCORDING TO YOUR UPDATE
try this cmp()
function cmp($a, $b) {
if($a['value'] == $b['value']) return 0;
return $a['value'] > $b['value'] ? 1 : -1;
}

First of all, by doing $obj["value"] = rand(..) you are assigning same array variable with different values multiple times. By the end of the loop, the variable will only contain one value.
You probably were trying to do this
$obj["value"][] = rand(5, 15); //This adds a new random item to the array contained in $obj['value'] each time
When you have an array items, you can sort them by using sort()[ascending] function rsort()[Descending[
$sorted = sort($obj["value"]);
print_r($sorted);

You loose this value
$obj["value"] = rand(5, 15);
at each iteration i guess. Check this link for php foreach loop:
PHP FOREACH
For sorting u can use sort function of php:
PHP SORT

Your foreach will not really generate anything useful. So I created an example array to illustrate the principle:
$array = array(
array(
'type' => 'type a',
'strength' => '10',
'value' => '12',
),
array(
'type' => 'type b',
'strength' => '12',
'value' => '15',
),
array(
'type' => 'type c',
'strength' => '11',
'value' => '6',
),
);
Now you want this multi dimensional array to be sorted by the value, so you would have a list if it was descending order, of type b, type a and then type c.
The function you are looking for is array_multisort(). Here a sample how to use it on this particular instance. First you need to create that sorter array for the function to do it's job. Then just use multisort and you are done.
$sorter = array();
foreach ($array as $key => $row) {
$sorter[$key] = $row['value'];
}
array_multisort($sorter, SORT_DESC, $array);
Now $array has been resorted according to the specifications. Use SORT_ASC to reverse the sorting.
Added code: Just for sake of completeness, here is your foreach code that should technically create what you wanted to in the first place:
$array = array();
foreach($cursor as $obj) {
$row = array();
$row['type'] = $obj['type'];
$row['strength'] = $obj['strenght'];
$row['value'] = rand(5, 15);
$array[] = $row;
}
Also you have a typo in strength :)

Since you are working with integer values, you can simply use this:
usort($newarr, "cmp");
function cmp($a, $b) {
return $b['value'] - $a['value'];
}
It will sort your keys in descending order, if you want to change that to ascending, swap $a and $b.
Sorting functions generally expect the compare function to return a value of 0 if the items are the same, <0 if the first argument is less than the second (whatever less means in the particular case) and >0 if the first argument is greater than the second.
With integer values this can simply be written as $a - $b or $b - $a.

Related

Sort an array of integers according to frequency of elements. If the frequency of two elements are same then they will be printed in ascending order

For example, input array is [9,1,9,1,3,9,1,2,9] output array will be [9,9,9,9,1,1,1,2,3].
Here's what I've tried below but not giving me expected result:
$array = [9,1,9,1,3,9,1,2,9];
$values = array_count_values($array);
arsort($values);
$popular = array_keys($values);
print_r(array_values($popular));
foreach ($values as $key => $val) {
echo $key.", ";
}
Output:
Array
(
[0] => 9
[1] => 1
[2] => 3
[3] => 2
)
9, 1, 3, 2,
If we loop the array_count_values then we can make sure they come in the correct order.
When there is two that is the same count I find all the same with array_intersect then foreach them and add in the correct order.
$array = [9,1,9,1,3,9,1,2,9];
$values = array_count_values($array);
arsort($values);
//var_dump($values);
$result =[];
$same = [];
foreach($values as $key => $val){
if(!in_array($key, array_keys($same))){
if(next($values) != $val){
$result = array_merge($result, array_fill(0, $val, $key));
}else{
$same = array_intersect($values, [$val]);
ksort($same);
foreach($same as $skey => $val){
$result = array_merge($result, array_fill(0, $val, $skey));
}
//var_dump($same);
}
}
}
var_dump($result);
https://3v4l.org/sk44Q
Try usort combined with array_count (php >= 7.0):
$array = [9,1,9,1,3,9,1,2,9];
$arrayCounts = array_count_values($array);
usort($array, function ($a, $b) use ($arrayCounts) {
$countA = $arrayCounts[$a] ?? 0;
$countB = $arrayCounts[$b] ?? 0;
if ($countA == $countB) {
return $a == $b ? 0 : ($a < $b ? -1 : 1);
}
return ($countA > $countB) ? -1 : 1;
});
You could use this sequence:
$arrayCounts = array_count_values($array);
usort($array, function ($a, $b) use ($arrayCounts) {
return $arrayCounts[$b] - $arrayCounts[$a] ?: $a - $b;
});
Now $array is sorted as requested.
The callback function that is passed as argument to usort should return a negative number when the two elements $a and $b should stay in that order (from left to right), or a positive number when they should be reversed. 0 when it does not matter.
The numeric expression that is returned in this particular callback function subtracts the frequencies of $a and $b. If $b occurs more, then that subtraction is positive, and that is returned. Similarly if the subtraction is negative, then that negative value is returned. Now when the frequencies are equal, the ?: operator kicks in and the expression after it is evaluated. That other subtraction also results in a positive or negative value, based on the original value itself. So $a - $b will be negative when $a < $b, which means the order can remain as it is (knowing that we already derived that their frequencies were equal).

Rank keys of an associative array according to their values (error in function)

I want to rank the keys of an associative array in php based upon their values. (top to down as 1, 2, 3....). Keys having same value will have same rank.
Here function getRanks() is meant to return an array containing keys and the ranks (number).
I expect it to return like this (this is sorted value wise in descending)
Array
(
[b] => 1
[a] => 2
[d] => 3
[c] => 3
[e] => 4
)
There is issue in assigning the ranks (values) in the $ranks array which is to be returned.
What am I doing wrong? Do these loops even do something?
Code:
$test = array('a'=> 50, 'b'=>60, 'c'=>20, 'd'=>20, 'e'=>10);
$json = json_encode($test);
print_r(getRanks($json));
function getRanks($json) {
$tmp_arr = json_decode($json, TRUE);
$ranks = array();
uasort($tmp_arr, function($a, $b){
return $a == $b ? 0 : $a > $b ? -1 : 1; //descending
});
$keys = array_keys($tmp_arr); //after sorting
$ranks = array_fill_keys($keys, 0); //copy keys
$ranks[$keys[0]] = 1; //first val => rank 1
//------- WORKS FINE UNTIL HERE ------------------
// need to fix the ranks assignment
for($i=1; $i<count($keys)-1; $i++) {
for($j=$i; $j < count($keys)-1; $j++) {
if($tmp_arr[$keys[$j]] == $tmp_arr[$keys[$j+1]]) {
$rank[$keys[$j]] = $i;
}
}
}
return $ranks;
}
Your approach seems unnecessarily complicated. In my version I kept the json-related copying part of it but finished it off in a simpler way:
function getRanks($json) {
$tmp_arr = json_decode($json, TRUE);
asort($tmp_arr);. // sort ascending
$i=0; $lv=null;$ranks = array();
foreach ($tmp_arr as $k=>$v) {
if ($v>$lv){ $i++; $lv=$v;}
$ranks[$k]=$i;
}
return $ranks;
}
See the demo here: https://rextester.com/LTOA23372
In a slightly modified version you you can also do the ranking in a descending order, see here: https://rextester.com/HESQP10053
I've also tried with another approach.
I think it may not be the good solution because of high memory & CPU time consumption.
For small arrays (in my case) it works fine.
(I've posted because it may be an answer)
It creates array of unique values and fetches ranks accordingly.
$test = array('a'=> 50, 'b'=>60, 'c'=>20, 'd'=>20, 'e'=>10);
$json = json_encode($test);
print_r(getRanks($json));
function getRanks($json) {
$tmp_arr = json_decode($json, TRUE);
arsort($tmp_arr);
$uniq_vals = array_values(array_unique($tmp_arr)); // unique values indexed numerically from 0
foreach ($tmp_arr as $k => $v) {
$tmp_arr[$k] = array_search($v, $uniq_vals) + 1; //as rank will start with 1
}
return $tmp_arr;
}
This is the simple thing that you can do it by using php array function check example given below.
<?php
$fruits = array("d" => "lemon", "a" => "orange", "b" => "banana", "c" => "apple");
asort($fruits);
foreach ($fruits as $key => $val) {
echo "$key = $val\n";
}
?>

Average from inner array

my task is to calculate average value from an array.
$arrayToTest = [[[1], 1], [[1,3,5,7], 4], [[2,5,4,1,2,3], 2.8],
[[-1,-1,-1,-1,-1], -1], [[4,23,84,12,76,34,-7,-23], 25.375]];
From inner array, so for example [1,3,5,7] and expected value is 4.
I have to use a function, I tried this:
function arrayAverage ($arrayToTest)
{
foreach($arrayToTest as $case)
foreach ($case as $item)
{
$arraySum = array_sum($item);
$arrayCount = array_count_values($item);
$average = $arraySum / $arrayCount;
return $average;
}
}
but it does not work. I feel I'm doing something wrong with calling the inner array.
Comment:
I assume that you wish to calculate the average values of the innermost arrays.
The solution below returns the average of each array - not the average of all arrays. But - of course you easily could calculate the average of all arrays.
Therefore the function arrayAverage(…) returns an array of average values instead of the average value of (only) the last array.
I declared the input array (arrayToTest) explicitely, for the reason that one can better see the array structure (array of arrays and scalars) like this.
Code:
<?php
$arrayToTest = array (
array(
array(1),
1
),
array(
array(1,3,5,7),
4
),
array(
array(2,5,4,1,2,3),
2.8
),
array(
array(-1,-1,-1,-1,-1),
-1
),
array(
array(4,23,84,12,76,34,-7,-23),
25.375
)
);
echo '<pre>'; print_r($arrayToTest); echo '</pre>';
$average = arrayAvarage ($arrayToTest);
echo '<pre>'; print_r($average); echo '</pre>';
function arrayAvarage ($arrayToTest) {
$result = array();
foreach($arrayToTest as $case) {
foreach ($case as $items) {
if (!is_array($items)) continue;
$result[] = array_sum($items) / count($items);
}
}
return $result;
}
?>
Result:
Array
(
[0] => 1
[1] => 4
[2] => 2.8333333333333
[3] => -1
[4] => 25.375
)
if your array contains internal arrays in the index 0, you can do this by:
function arrayAvarage ($arrayToTest)
{
$out_put_arr = array();
foreach($arrayToTest as $case)
{
$arraySum = array_sum($case[0]);
$arrayCount = array_count_values($case[0]);
$avarage = $arraySum / $arrayCount;
$out_put_arr[]= $avarage;
}
return $out_put_arr;
}
so the loop for the main array, each item in the main array will give you array, and int $case[0] = [1,3,5,7] and $case[1] = 4, also you shouldn't return in for loop because this will return the first average only. so you can declare new array to fill with all averages.
function average($array){
return array_sum($array) / count($array);
}
foreach($arrayToTest as $array){
echo "Average: " . average($array[0]);
}
You should look at the first element of the $case array, which is the actual place where the array with values is situated. Note that you can also use the array_sum function.
Also, you should not return just like that, because that will interrupt the function from doing anything more. So, only return when you really want to do that.
As you already have expected values, I see no reason why your function should return those averages again. Instead it could verify the correctness of these expected values, and return the index of the array when that comparison fails.
function arrayAverage ($arrayToTest)
{
foreach($arrayToTest as $index => $case) {
$average = array_sum($case[0]) / count($case[0]);
if ($average !== $case[1]) {
return $index; // not expected value
}
}
return false; // all averages are equal to expected value
}
So, the above function will return FALSE when all averages are as expected. Otherwise it will return the index of the first mismatch.

Is there a way to loop through a multidimensional array without knowing it's depth?

So far, if I have to loop through a multidimensional array, I use a foreach loop for each dimension.
e.g for two dimensions
foreach($array as $key=>$value)
{
foreach($value as $k2=>$v2)
{
echo
}
}
What do I do when I don't know the depth of the array? ie the depth is variable.
The only thing I can think of is to code a whole stack of loops and to break the loop if the next value is not an array.This seems a little silly.
Is there a better way?
Yes, you can use recursion. Here's an example where you output all the elements in an array:
function printAll($a) {
if (!is_array($a)) {
echo $a, ' ';
return;
}
foreach($a as $v) {
printAll($v);
}
}
$array = array('hello',
array('world',
'!',
array('whats'),
'up'),
array('?'));
printAll($array);
What you should always remember when doing recursion is that you need a base case where you won't go any deeper.
I like to check for the base case before continuing the function. That's a common idiom, but is not strictly necessary. You can just as well check in the foreach loop if you should output or do a recursive call, but I often find the code to be harder to maintain that way.
The "distance" between your current input and the base case is called a variant and is an integer. The variant should be strictly decreasing in every recursive call. The variant in the previous example is the depth of $a. If you don't think about the variant you risk ending up with infinite recursions and eventually the script will die due to a stack overflow. It's not uncommon to document exactly what the variant is in a comment before recursive functions.
You can do the below function for loop-through-a-multidimensional-array-without-knowing-its-depth
// recursive function loop through the dimensional array
function loop($array){
//loop each row of array
foreach($array as $key => $value)
{
//if the value is array, it will do the recursive
if(is_array($value) ) $array[$key] = loop($array[$key]);
if(!is_array($value))
{
// you can do your algorithm here
// example:
$array[$key] = (string) $value; // cast value to string data type
}
}
return $array;
}
by using above function, it will go through each of the multi dimensional array, below is the sample array you could pass to loop function :
//array sample to pass to loop() function
$data = [
'invoice' => [
'bill_information' => [
'price' => 200.00,
'quantity' => 5
],
'price_per_quantity' => 50.00
],
'user_id' => 20
];
// then you can pass it like this :
$result = loop($data);
var_dump($result);
//it will convert all the value to string for this example purpose
You can use recursion for this problem:
Here is one example
$array = array(1 => array(1 => "a", 2 => array(1 => "b", 2 => "c", 3 => array(1 => "final value"))));
//print_r($array);
printAllValues($array);
function printAllValues($arr) {
if(!is_array($arr)) {
echo '<br />' . $arr;
return;
}
foreach($arr as $k => $v) {
printAllValues($v);
}
}
It will use recursion to loop through array
It will print like
a
b
c
final value
Simple function inside array_walk_recursive to show the level of nesting and the keys and values:
array_walk_recursive($array, function($v, $k) {
static $l = 0;
echo "Level " . $l++ . ": $k => $v\n";
});
Another one showing use with a reference to get a result:
array_walk_recursive($array, function($v) use(&$result) {
$result[] = $v;
});
Based on previous recursion examples, here is a function that keeps an array of the path of keys a value is under, in case you need to know how you got there:
function recurse($a,$keys=array())
{
if (!is_array($a))
{
echo implode("-", $keys)." => $a <br>";
return;
}
foreach($a as $k=>$v)
{
$newkeys = array_merge($keys,array($k));
recurse($v,$newkeys);
}
}
recurse($array);

Sorting Elements of an Array by number in string?

I have a .txt file that looks like this:
john 1000
mike 8393
tom 1000
bob 233
roger 2
daniel 233
... ...
I need to put every line into array and sort by number size without losing what name goes with what number.
Also some numbers are repeating through the file.
Finally I want to echo elements of an array sorted by number size.
You could break each line into an array of integers and strings (split your current strings on the space) and then sort the array using ksort (assuming the key is the integer) and there you go!
You can of course alternatively use a more robust sort, but this will get you there.
You can then print it by using print_r to print the human readable version of the array
http://php.net/manual/en/array.sorting.php
http://php.net/manual/en/function.print-r.php
If you're running php 5.3+
$fileLines = explode("\n", $fileContents);
usort($fileLines, function($a, $b) {
$aNumber = (int)substr($a, strpos($a, ' ')+1);
$bNumber = (int)substr($b, strpos($b, ' ')+1);
if($aNumber === $bNumber) {
return 0;
}
return $aNumber > $bNumber ? 1 : -1;
});
If you're running a lower version, convert the function into a global function and provide the name as a callback string.
usort($fileLines, 'sortlines');
function sortlines($a, $b) {
$aNumber = (int)substr($a, strpos($a, ' ')+1);
$bNumber = (int)substr($b, strpos($b, ' ')+1);
if($aNumber === $bNumber) {
return 0;
}
return $aNumber > $bNumber ? 1 : -1;
}
then
var_dump($fileLines);
You can go three ways:
1.Create two-dimensional array with numeric indexes and sort it with usort(); and lambda function
$list = array(array('name' => 'john', 'number' => 1000),
array('name' => 'mike', 'number' => 8393),
array('name' => 'tom', 'number' => 1000)
);
$by = 'number';
usort($list, function($first, $second) use ($by)
{
if ($first[$by] > $second[$by] { return 1; }
elseif (first[$by] < $second[$by]) { return -1; }
return 0;
}
);
2.Create array with indexes as names and sort it with sort();
$list = array('john' => 1000,
'mike' => 8393,
'tom' => 1000
);
sort($list);
3.Create array with indexes as numbers and sort it with ksort();
$list = array(1000 => 'john',
8393 => 'mike',
1000 => 'tom'
);
ksort($list);
If you choose first way you can address the element as
$list[0][name] = 'bob'
$list[1][number] = 1000;
Second
$list['john'] = 1001;
Third
$list[1000] = 'bob';
In last two ways you should use foreach to go through array
Use
print_r($list);
or
var_dump($list);
to print the array, or create your own code
P.S. Don't forget thar calling usort with lambda function is PHP 5.3 way, if you use earlier version of PHP, you should use discrete function

Categories