Sort a multi-dimensional array on two subarray values - php

I would like to sort an array on eta ASC then ab ASC.
Here's the input array:
$res_ubr=array(
"0"=>array(
"ab"=>"jdfd",
"bb"=>"iweuiru",
"eta"=>4
),
"1"=>array(
"ab"=>"dsdjdfd",
"bb"=>"iuiru",
"eta"=>5
),
"2"=>array(
"ab"=>"jdgfd",
"bb"=>"edfguiru",
"eta"=>2
),
"3"=>array(
"ab"=>"err",
"bb"=>"iuierru",
"eta"=>2
),
"4"=>array(
"ab"=>"fdfdf",
"bb"=>"dfdf",
"eta"=>3
)
);
Here is my expected result:
[
['ab'=>'err', 'bb'=>'iuierru', 'eta'=>2],
['ab'=>'jdgfd', 'bb'=>'edfguiru', 'eta'=>2],
['ab'=>'fdfdf', 'bb'=>'dfdf', 'eta'=>3],
['ab'=>'jdfd', 'bb'=>'iweuiru', 'eta'=>4],
['ab'=>'dsdjdfd', 'bb'=>'iuiru', 'eta'=>5]
]
This is what I've tried:
$res2=$res_ubr;
$temp=array();
foreach($res_ubr as $key=>$val){
foreach($res2 as $k=>$v){
if($val['eta']>$v['eta']){
$temp[]=$res_ubr[$key];
$res_ubr[$key] =$res_ubr[$k];
$res_ubr[$k]=$temp[];
}
}
}
print_r($res_ubr);
For context, the eta value is the time in minutes that the cab is expected to arrive.

You only need to assign a unique temporary key. There are two eta's with a value of 2 in your input array, so you will need to provide a "tie breaker". I am using the ab value to dictate the sorting order when two duplicate etas exist. I'm arbitrarily padding the eta values to an 8 digit number to ensure that numeric sorting occurs as intended. Without left padding with zeros, 10 would come before 2 because the temporary keys will be sorted as strings (one character at a time from left to right) and 1 comes before 2.
foreach($res_ubr as $sub){
$result[str_pad($sub["eta"],8,'0',STR_PAD_LEFT).$sub["ab"]]=$sub;
}
ksort($result);
var_export(array_values($result));
Because you say the eta are taxi arrival times, I'd bet your business that no eta is going to be in the tens of millions of minutes, so you can reduce the padding amount from 8 down to what ever is reasonably expected.
Keep in mind that if there is ever a duplicate temporary key (a matching pair of eta & ab), then my method will overwrite the first element with the second and you will have missing data. If this is a remote possibility, you should also append bb to the temporary key. If this, again, at risk of being a duplicate, you should add a randomly generated string of numbers to prevent your data being erased.
Output with eta ASC, ab ASC:
array (
0 =>
array (
'ab' => 'err',
'bb' => 'iuierru',
'eta' => 2,
),
1 =>
array (
'ab' => 'jdgfd',
'bb' => 'edfguiru',
'eta' => 2,
),
2 =>
array (
'ab' => 'fdfdf',
'bb' => 'dfdf',
'eta' => 3,
),
3 =>
array (
'ab' => 'jdfd',
'bb' => 'iweuiru',
'eta' => 4,
),
4 =>
array (
'ab' => 'dsdjdfd',
'bb' => 'iuiru',
'eta' => 5,
),
)

Related

PHP Parsing ELA Temperature Sensor raw data

I have successfully converted the positive temperature data from below raw data
array (
'timestamp' => '2020-06-11T11:09:21.335Z',
'type' => 'Unknown',
'mac' => 'F64BB46181EF',
'bleName' => 'P RHT 900350',
'rssi' => -63,
'rawData' => '02010605166E2AC90A04166F2A240D09502052485420393030333530',
)
$cutdata = str_split($rawData,2);
$humidity_cut = hexdec($cutdata[13]);
$x_cut = $cutdata[8].$cutdata[7]; //gives 0AC9
$c_cut = hexdec($x_cut);
$temp_cut = $c_cut/100;
echo $temp_cut;exit;
But when i am getting negative temperature values it giving me issues it increase the temp value more then 600
Here is the negative Temp Raw Data
array (
'timestamp' => '2020-07-03T10:05:53.049Z',
'type' => 'Unknown',
'mac' => 'EDF2F589DCAE',
'bleName' => 'P RHT 900351',
'rssi' => -79,
'rawData' => '02010605166E2AB4FA04166F2A310D09502052485420393030333531',
)
I have asked the support team they said
You have to do a 2 complement, which is reversing all the bits, and add 1 in binary.
I'm assuming that the output is OK, but for a 32 bit number, this code checks if the high bit is set (using & 32768) and if it is, it xors the number with 65535 (all 16 bits set) to invert it and then just adds 1 (the result is then made a -ve number)...
if ( $c_cut & 32768 ) {
$c_cut = -(($c_cut ^ 65535)+1);
}
which gives -13.56 as the result.

Is this normal behaviour in php arrays? Array size get shortened when using numbered indexes out of order

So I'm learning Php, so as I was messing around with arrays to see how they work, I stumbled into this when I made two arrays.
$TestArray1 = array( 1 => 1, "string" => "string", 24, "other", 2 => 6, 8);
$TestArray2 = array( 6 => 1, "string" => "string", 24, "other", 1 => 6, 8);
But when I print them out with print_r() this is what I get (this also happens with var_dump by the way)
Array ( [1] => 1 [string] => string [2] => 6 [3] => other [4] => 8 )
Array ( [6] => 1 [string] => string [7] => 24 [8] => other [1] => 6 [9] => 8 )
As far as I can tell, by putting the two in the second array it overwrites the next possible spot with no key and then keeps going, shortening the array. So I thought that meant that if I use a 1 it would put it at the start but that does not happen either.
Is this normal or is there something wrong with my php installation?
Im using Ampps in windows 10 with php 7.3.
Thanks in advance
Good question.
What's happening is that when determining automatic numeric indexes, PHP will look to the largest numeric index added and increment it (or use 0 if there are none).
The key is optional. If it is not specified, PHP will use the increment of the largest previously used integer key.
What's happening with your first array is that as it is evaluated left-to-right, 24 is inserted at index 2 because the last numeric index was 1 => 1.
Then when it gets to 2 => 6, it overwrites the previous value at index 2. This is why 24 is missing from your first array.
If multiple elements in the array declaration use the same key, only the last one will be used as all others are overwritten.
Here's a breakdown
$TestArray1 = [1 => 6]; // Array( [1] => 6 )
// no index, so use last numeric + 1
$TestArray1[] = 24; // Array( [1] => 6, [2] => 24 )
$TestArray1[2] = 6; // Array( [1] => 6, [2] => 6 )
When you manually add numeric indexes that are lower than previous ones (ie $TestArray2), they will be added as provided but their position will be later.
This is because PHP arrays are really maps that just pretend to be indexed arrays sometimes, depending on what's in them.
References are from the PHP manual page for Arrays

PHP: one dimensional array to multidimensional array with element multiplication

Is there an easy way to transform a one dimensional array into multidimensional array and along with that add a certain element to the newly created sub-arrays?
The countrycode always has 2 digits and appears only once before a set of locations. That's the element that I'd like to duplicate and add it to every subarray as shown below.
Thanks in advance!
That's what I have:
0 => AT
1 => Vienna-S03-I01
2 => 28 Users
3 => Vienna-S03-I02
4 => 25 Users
5 => Vienna-S03-I03
6 => 24 Users
7 => AU
8 => Sydney-S01-I01
9 => 45 Users
10 => BE
11 => Brussels-S01-I01
12 => 30 Users
13 => Brussels-S01-I02
14 => 37 Users
That's what I'd like to have:
0 =>
0 => AT
1 => Vienna-S03-I01
2 => 28 Users
1 =>
0 => AT
1 => Vienna-S03-I02
2 => 25 Users
2 =>
0 => AT
1 => Vienna-S03-I03
2 => 24 Users
3 =>
0 => AU
1 => Sydney-S01-I01
2 => 45 Users
4 =>
0 => BE
1 => Brussels-S01-I01
2 => 30 Users
5 =>
0 => BE
1 => Brussels-S01-I02
2 => 37 Users
If there is a guarantee that array will always follow rule you demonstrated, then code is below. Otherwise few condition check should be added there to make sure that we have proper value type in every given $item.
$array = ['AT',
'Vienna-S03-I01',
'28 Users',
'Vienna-S03-I02',
'25 Users',
'Vienna-S03-I03',
'24 Users',
'AU',
'Sydney-S01-I01',
'45 Users',
'BE',
'Brussels-S01-I01',
'30 Users',
'Brussels-S01-I02',
'37 Users'];
$code='none';
$result=[];
$resIndex=-1;
$swing=false;
foreach($array as $item){
if (strlen($item)===2){
$code=$item;
}else{
if ($swing===false){
$resIndex++;
$result[$resIndex][]=$code;
}
$result[$resIndex][]=$item;
$swing=!$swing;
}
}
print_r($result);
?>
This is a very light method in terms of function calls and variables. isset() is a very swift function call, so there will be minimal drag there. The snippet is condensed by declaring multiple variables in a single line ($result[++$i][]=$k=$v;) and by incrementing the counter (++$i) inside of the result array declarations.
Code: (Demo)
$array = ['AT',
'Vienna-S03-I01',
'28 Users',
'Vienna-S03-I02',
'25 Users',
'Vienna-S03-I03',
'24 Users',
'AU',
'Sydney-S01-I01',
'45 Users',
'BE',
'Brussels-S01-I01',
'30 Users',
'Brussels-S01-I02',
'37 Users'];
$i=-1;
foreach($array as $v){
if(strlen($v)==2){ // check for new Country ID
$result[++$i][]=$k=$v; // preserve Country ID as $k, store as a new batch
}else{
if(isset($result[$i][2])){ // when three elements in current batch, start new...
$result[++$i][]=$k; // increment counter and store preserved Country ID
}
$result[$i][]=$v; // add $v to batch
}
}
var_export($result);
p.s. as a matter of further micro-optimization, you could swap out strlen() for another isset() call -- but that is a bit less intuitive:
$i=-1;
foreach($array as $v){
if(!isset($v[2])){ // check for existence of character at offset 2
$result[++$i][]=$k=$v;
}else{
if(isset($result[$i][2])){
$result[++$i][]=$k;
}
$result[$i][]=$v;
}
}
This is a functional approach that doesn't need to iterate for each element in the array. I just love array_splice() and its fantastic dual action of extracting elements and shortening the input array:
// array_splice modifies the original array (shortens it) and returns the removed elements
while($array){
if(strlen($array[0])==2){ // if the first value is a Country id
$id=$array[0]; // preserve id
$result[]=array_splice($array,0,3); // cut away and preserve first three elements
}else{
$result[]=[$id]+array_splice($array,0,2); // store id and first two elements
}
}
var_export($result);
...and finally, my DRYest method:
while($array){
if(strlen($array[0])==2){ // if the first value is a Country id
$id=array_splice($array,0,1);
}
$result[]=array_merge($id,array_splice($array,0,2)); // store preserved id and two elements
}
var_export($result);

Why does my associative array turn into an indexed one halfway through? and then changes back to an associative one?

I'm writing this code that changes an indexed array to an associative array. It works for all the states I've checked, except california. For california, its an associative array for a little while, and then it changes to an indexed array out of nowhere and back to an associative one at the end. When I echo the individual indexes, I get the values I'm looking to use in the associative array so I'm not sure why it doesn't stick. Can anyone help?
$ids_file = file_get_contents("http://waterdata.usgs.gov/ca/nwis/current?index_pmcode_STATION_NM=1&index_pmcode_DATETIME=2&index_pmcode_72019=72&index_pmcode_00065=261&index_pmcode_00060=191&group_key=NONE&format=sitefile_output&sitefile_output_format=rdb&column_name=agency_cd&column_name=site_no&column_name=station_nm&column_name=dec_lat_va&column_name=dec_long_va&column_name=coord_acy_cd&column_name=dec_coord_datum_cd&column_name=alt_va&column_name=alt_acy_va&column_name=alt_datum_cd&sort_key_2=site_no&html_table_group_key=NONE&rdb_compression=file&list_of_search_criteria=realtime_parameter_selection");
$gages = explode("\t",$ids_file);
//this provides the randomized list of gages to pick from
for($i=19;$i<count($gages);$i=$i+9){
$gagenew = array($gages[$i]=>$gages[$i+1]);
$gagenum = array_merge($gagenum,$gagenew);
}
this is a small sample of what I get when I echo $gagenum
[09526200] => YPSILANTI CANAL NEAR WINTERHAVEN, CA
[09527590] => COACHELLA CANAL ABV ALL-AMERICAN CANAL DIV
[09527594] => COACHELLA CANAL NEAR NILAND, CA
[09527597] => COACHELLA CANAL NEAR DESERT BEACH, CA
[09527700] => ALL-AMERICAN CANAL BELOW DROP 2 RESERVOIR OUTLET
[09530000] => RESERVATION MAIN DRAIN NO. 4 NEAR YUMA, AZ
[09530500] => DRAIN 8-B NEAR WINTERHAVEN, CA
[0] => BOREHOLE SPG CHANNEL NR TECOPA HOT SPGS, CA
[1] => AMARGOSA RV AT TECOPA, CA
[2] => AMARGOSA RV ABV CHINA RANCH WASH NR TECOPA, CA
[3] => WILLOW CK AT CHINA RANCH, CA
[4] => SALT C NR MECCA
[5] => ALAMO R NR NILAND CA
[6] => NEW R AT INTERNATIONAL BOUNDARY AT CALEXICO CA
[7] => NEW R NR WESTMORLAND CA
[8] => SNOW C NR WHITE WATER CA
[9] => FALLS C NR WHITE WATER CA
[10] => WHITEWATER R A WINDY POINT MAIN CHANNEL CA
[11] => WHITEWATER R A WINDY POINT OVERFLOW CHANNEL CA
see where is it changes to an indexed array out of nowhere?
Your problem comes from the behavior that naturally occurs in array_merge():
Merges the elements of one or more arrays together so that the values of one are appended to the end of the previous one. It returns the resulting array.
If the input arrays have the same string keys, then the later value for that key will overwrite the previous one. If, however, the arrays contain numeric keys, the later value will not overwrite the original value, but will be appended.
Values in the input array with numeric keys will be renumbered with incrementing keys starting from zero in the result array.
PHP array_merge() Reference
You could try declaring all your keys as strings.
Edit
Oddly enough, my testing can't get it to behave. Even stranger than that, I found a simpler method of joining arrays that I never even considered.
//Replace array_merge() with this
$gagenum += $gagenew;
When you print_r($gagenum) it should be the two arrays joined with the keys they had. You could either leave it at that or use the result for a var_dump($gagenum) and debug it.
Experimentation
Further tests with the differences between array_merge() and the concatenation method $array1 + $array2 should help give a better understanding of the difference.
<?php
$int1 = array( 2 => 'george', 3 => 'sam', 4 => 'bob' );
$int2 = array( 0 => 'sue', 1 => 'debbie', 2 => 'sally' );
$str1 = array( 'one' => 'ken', 'two' => 'tom', 'three' => 'phil' );
$str2 = array( 'three' => 'jenny', 'four' => 'alice', 'five' => 'jan' );
$mergeii = array_merge( $int1, $int2 );
$mergeis = array_merge( $int1, $str1 );
$mergesi = array_merge( $str1, $int1 );
$mergess = array_merge( $str1, $str2 );
$concatii = $int1 + $int2;
$concatis = $int1 + $str1;
$concatsi = $str1 + $int1;
$concatss = $str1 + $str2;
// Merge Int/Int - Numeric Keys Reassigned Starting From [0], with $int1 first. No Overwrites.
echo "Merge Int/Int:\r\n"; print_r($mergeii);
// Merge Int/Str - Numeric Keys Reassigned Starting From [0], $int1 first. No Overwrites.
echo "\r\nMerge Int/Str:\r\n"; print_r($mergeis);
// Merge Str/Int - Numeric Keys Reassigned Again, $str1 first. No Overwrites.
echo "\r\nMerge Str/Int:\r\n"; print_r($mergesi);
// Merge Str/Str - No Numeric Keys. Value of Key 'Three' overwritten with $str2 value. Bye bye, 'phil'.
echo "\r\nMerge Str/Str:\r\n"; print_r($mergess);
// Concatenate Int/Int - Numeric Keys Preserved. Value of Key (int)2 from $int1 Preserved, 'sally' lost.
echo "\r\nConcatenate Int/Int:\r\n"; print_r($concatii);
// Concatenate Int/Str - Expected results, All Key/Value Pairs Preserved
echo "\r\nConcatenate Int/Str:\r\n"; print_r($concatis);
// Concatenate Str/Int - Expected results, All Key/value Pairs Preserved
echo "\r\nConcatenate Str/Int:\r\n"; print_r($concatsi);
// Concatenate Str/Str - Expected results, Value of Key (str)'three' from $int1 Preserved, 'jenny' lost.
echo "\r\nConcatenate Str/Str:\r\n"; print_r($concatss);
?>
So array_merge() prioritizes the second argument over the first, and the concatenation method prioritizes the first over the second. array_merge() for some reason still treats numeral-like strings as int, even if explicitly declared. This was by changing the line to:
$int1 = array( (string)'2' => 'george', (string)'3' => 'sam', (string)'4' => 'bob' );
var_dump($int1) correctly shows the keys as strings before merging, yet still reassigns the keys numerically. Bug perhaps?
Which one is Faster
On to the performance test! Tests were done without altering the original array, was tested using 8 for loops with this format:
$start1 = microtime(true);
for( $x = 0; $x < 100000; ++$x)
{
$merged = array_merge($int1, $int2);
}
$end1 = microtime(true);
Array Merge Function Average time was anywhere between 0.15 - 0.19 seconds, with $int1/$int2 usually being the lowest by -0.01, where $str1/$str2 was usually +0.02 seconds. $str1/$int1 and $int1/$str1 flip-flopped between the middle average 0.16-0.17.
Concatenation Method Average time was anywhere between 0.05 - 0.07. Once again, $int1/$int2 was the leader by -0.01 second. $str1/$str2 was also +0.02 seconds longer typically.
So the concatenation method is definitely a better method, especially if you want to preserve array keys. Use array_merge() when you want to keep all values, but want to restructure two different int arrays into a sequential array for scripts that could use a good for loop.

Best way to iterate to a part of an array based on value?

I have an php-array like this:
$students = array(
array(1, 'James', 'Brown', 10, 50),
array(2, 'John', 'Doe', 10, 40),
array(3, 'Adam', 'Works', 10, 40));
Where the columns represent id, first name, last name, category, and level. As example I use three rows... in practice I use many more.
Now I need to group print the results based on column (category or level) and a specific value. For example, I want to print (echo) all students in category 10. I can loop through each record and check the category; if 10 then print. The same applies for level 40. Loop through the whole set again and check the level; if 40 then print.
With an eye on performance, is there a smarter way to achive this? For example, first sort or filter?
I could choose to query the database many times but I doubt if this is the best way. I guess manipulating the array is better.
Thanks for your support!
The most straightforward way to do this is with the array_filter() function. But if these values come from a database, you're probably better off writing better queries.
$ cat array-filter.php
<?php
$students = array(
array(1, 'James', 'Brown', 10, 50),
array(2, 'John', 'Doe', 10, 40),
array(3, 'Adam', 'Works', 10, 40));
$level_40 = array_filter($students, function($a) {return $a[4]==40 ? true : false;});
print_r($level_40);
?>
$ php array-filter.php
Array
(
[1] => Array
(
[0] => 2
[1] => John
[2] => Doe
[3] => 10
[4] => 40
)
[2] => Array
(
[0] => 3
[1] => Adam
[2] => Works
[3] => 10
[4] => 40
)
)
The callback function just compares the fifth column to the integer literal 40. The fifth column has index 4, not 5, hence the expression $a[4]==40 ?.
If you're getting this data from a database, you're probably better off writing better SQL. To get all the students whose level is 40, you'd write something like
select *
from students
where level = 40
order by student_id;
The result set will contain exactly the right rows, so you don't need to filter them. And they'll be in exactly the right order, so you don't need to sort them.

Categories