How SplPriorityQueue works when priority is not an integer? - php

I was wondering how SplPriorityQueue works when priority is string or int. Quick example:
$queue = new \SplPriorityQueue();
$queue->insert('b', 5);
$queue->insert('c', 5);
$queue->insert('d', 1);
$queue->insert('a', 10);
$queue->insert('1', 'a');
$queue->insert('2', 'b');
print_r($queue);
Output:
Array
(
[5] => a
[4] => b
[3] => c
[2] => d
[1] => 2
[0] => 1
)
Question: why items with int priority are listed first (i.e. a b c d)? When priority is string (items 1 2), is b considered greater than a?

This is determined by SplPriorityQueue::compare(). The documentation states about its return value:
Result of the comparison, positive integer if priority1 is greater
than priority2, 0 if they are equal, negative integer otherwise.
Note:
Multiple elements with the same priority will get dequeued in no particular order.
Note, that the parameters priority1 and priority2 are declared as mixed and there is no mention of converting to int.
This means, the usual rules for > apply (see comparison operator documentation):
string compared to string: lexical comparison (numerical if both strings are numerical)
int compared to int: numerical comparison
string compared to int: string is converted to number, numerical comparison
(int)'a' and (int)'b' resolves to 0, this is why these items come last after all numbers.
These are the relevant comparisons for your example:
php > var_dump(1 > 'a');
bool(true)
php > var_dump(1 > 'b');
bool(true)
php > var_dump('b' > 'a');
bool(true)

Related

Unexpected behaviour in an array with some values

My code is:
$arr=[02,05,07,08,09];
print_r($arr)
and output is:
Array
(
[0] => 2
[1] => 5
[2] => 7
[3] => 0
[4] => 0
)
Why it converts 08 and 09 to 0??
Numbers beginning with a zero are considered to be in base 8.
See PHP docs: Integers - Syntax.
To use octal notation, precede the number with a 0 (zero). To use hexadecimal notation precede the number with 0x. To use binary notation precede the number with 0b.
its considered as octal number. because you are starting it from zero.
$a = 1234; // decimal number
$a = -123; // a negative number
$a = 0123; // octal number (equivalent to 83 decimal)
$a = 0x1A; // hexadecimal number (equivalent to 26 decimal)
$a = 0b11111111; // binary number (equivalent to 255 decimal)

Floating point test assertion - why do these "identical" arrays fail?

I'm using assertSame() in PHPUnit to compare a database result with expected values. The results are floating point numbers.
PHPUnit returns this message (but I can't spot any differences):
Failed asserting that Array (
'1_1' => 11.111111111111
'1_2' => 33.333333333333
'1_3' => 55.555555555556
'1_4' => 0.0
'1_5' => null
'1_total' => 100.0
) is identical to Array (
'1_1' => 11.111111111111
'1_2' => 33.333333333333
'1_3' => 55.555555555556
'1_4' => 0.0
'1_5' => null
'1_total' => 100.0
)
Why is this failing and what is the correct way to compare an arrays of floating point values?
assertEquals has a $floating_delta argument for this type of cases:
$this->assertEquals($expected_array, $actual_array, '', 0.00001);
PHPUnit docs
The problem is almost certainly floating point precision. In the print_r, only so many digits are shown. If all significant bits were displayed, the situation is probably something like this:
Failed asserting that Array (
'1_1' => 11.1111111111110347
'1_2' => 33.3333333333331678
'1_3' => 55.5555555555562773
'1_4' => 0.0
'1_5' => null
'1_total' => 100.0
) is identical to Array (
'1_1' => 11.1111111111110346
'1_2' => 33.3333333333331679
'1_3' => 55.5555555555562771
'1_4' => 0.0
'1_5' => null
'1_total' => 100.0
)
Every floating point comparison—especially equality—must consider the lack of infinite precision.
if ($var == 0.005) /* just plain wrong! */
if (abs ($var, 0.005) < 0.001) /* more correct */
if (abs ($var, 0.005) < 0.0001) /* maybe more correct, depending on application */
if (abs ($var, 0.005) < 0.0000001) /* possibly more appropriate */
Unless anyone has a better suggestion, I will assume this is a floating point precision error, and follow the PHP manual advice: do not compare floating point numbers directly for equality.
So my own solution is to round the array values before comparison.

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.

php split integer into smaller parts

i'm working on a project that will need to have everything shown with barcodes, so I've generated 7 numbers for EAN8 algorithm and now have to get these 7 numbers seperately, right now i'm using for the generation
$codeint = mt_rand(1000000, 9999999);
and I need to get this 7 numbers each seperately so I can calculate the checksum for EAN8, how can i split this integer to 7 parts, for example
12345678 to
arr[0]=1
arr[1]=2
arr[2]=3
arr[3]=4
arr[4]=5
arr[5]=6
arr[6]=7
any help would be appreciated..
also I think that I'm becoming crazy :D because I already tried most of the solutions you gave me here before and something is not working like it should work, for example:
$codeint = mt_rand(1000000, 9999999);
echo $codeint."c</br>";
echo $codeint[1];
echo $codeint[2];
echo $codeint[3];
gives me :
9082573c
empty row
empty row
empty row
solved! $codeint = (string)(mt_rand(1000000, 9999999));
Try to use str_split() function:
$var = 1234567;
print_r(str_split($var));
Result:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
[6] => 7
)
There are two ways to do this, one of which is reasonably unique to PHP:
1) In PHP, you can treat an integer value as a string and then index into the individual digits:
$digits = "$codeint";
// access a digit using intval($digits[3])
2) However, the much more elegant way is to use actual integer division and a little knowledge about mathematical identities of digits, namely in a number 123, each place value is composed of ascending powers of 10, i.e.: 1 * 10^2 + 2 * 10^1 + 3 * 10^0.
Consequently, dividing by powers of 10 will permit you to access each digit in turn.
it's basic math you can divide them in loop by 10
12345678 is 8*10^1 + 7*10^2 + 6*10^3...
the other option is cast it to char array and then just get it as char
Edit
After #HamZa DzCyberDeV suggestion
$string = '12345678';
echo "<pre>"; print_r (str_split($string));
But in mind it comes like below but your suggestion is better one.
If you're getting string from your function then you can use below one
$string = '12345678';
$arr = explode(",", chunk_split($string, 1, ','));
$len = count($arr);
unset($arr[$len-1]);
echo "<pre>";
print_r($arr);
and output is
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
[6] => 7
[7] => 8
)
okay what you can do is
Type cast to string with prefill 0
this is how it works
$sinteger = (string)$integer;
$arrsize = 0 ;
for (i=strlen($sinteger), i == 0 ; i--)
{
arr[$arrsize]=$sinteger[i];
$arrsize++;
}
And then what is left you can prefill with zip.
I am sure you can manage the order reverse or previous. but this is simple approach.

Split an array into two evenly by it's values

array
1703 => float 15916.19738
5129 => float 11799.15419
33 => float 11173.49945
1914 => float 8439.45987
2291 => float 6284.22271
5134 => float 5963.14065
5509 => float 5169.85755
4355 => float 5153.80867
2078 => float 3932.79341
31 => float 3924.09928
5433 => float 2718.7711
3172 => float 2146.1932
1896 => float 2141.36021
759 => float 1453.5501
2045 => float 1320.74681
5873 => float 1222.7448
2044 => float 1194.4903
6479 => float 1074.1714
5299 => float 950.872
3315 => float 878.06602
6193 => float 847.3372
1874 => float 813.816
1482 => float 330.6422
6395 => float 312.1545
6265 => float 165.9224
6311 => float 122.8785
6288 => float 26.5426
I would like to distribute this array into two arrays both ending up with a grand total (from the float values) to be about the same. I tried K-Clustering but that distributes higher values onto one array and lower values onto the other array. I'm pretty much trying to create a baseball team with even player skills.
Step 1: Split the players into two teams. It doesn't really matter how you do this, but you could do every other one.
Step 2: Randomly switch two players only if it makes the teams more even.
Step 3: Repeat step 2 until it converges to equality.
$diff = array_sum($teams[0]) - array_sum($teams[1]);
for ($i = 0; $i < 1000 && $diff != 0; ++$i)
{
$r1 = rand(0, 8); // assumes nine players on each team
$r2 = rand(0, 8);
$new_diff = $diff - ($teams[0][$r1] - $teams[1][$r2]) * 2;
if (abs($new_diff) < abs($diff))
{
// if the switch makes the teams more equal, then swap
$tmp = $teams[0][$r1];
$teams[0][$r1] = $teams[1][$r2];
$teams[1][$r2] = $tmp;
var_dump(abs($new_diff));
$diff = $new_diff;
}
}
You'll have to adapt that code to your own structures, but it should be simple.
Here's a sample output:
int(20)
int(4)
int(0)
I was using integers from 0 to 100 to rate each player. Notice how it gradually converges to equality, although an end result of 0 is not guaranteed.
You can stop the process after a fixed interval or until it reaches some threshold.
There are more scientific methods you could use, but this works well.
This is extremely simplistic, but have you considered just doing it like a draft? With the array sorted as in your example, Team A gets array[0], Team B gets array[1] and array[2] the next two picks go to Team A, and so on.
For the example you give, I got one team with ~50,000 and the other with ~45,000.

Categories