Wednesday, January 27, 2010

PHP: array_rand does not shuffle!

PHP function array_rand() does not shuffle results anymore and behaves like array_keys() in some circumstances.

Yes, that is correct. The function array_rand() gets random key (or keys) from given array. Funny thing is that it does not shuffle the keys anymore. So the following code:

<?php
$a 
= array (=> 'key 5''key after 5'=> 'key 3''a' => 'key a'=> 'key 1'=> 'key 0''key after 0',);
print_r($a);
print_r(array_rand($a5));
print_r(array_rand($acount($a));
print_r(array_keys($a);
?>

would produce that result:

Array
(
    [5] => key 5
    [6] => key after 5
    [3] => key 3
    [a] => key a
    [1] => key 1
    [0] => key 0
    [7] => key after 0
)
Array
(
    [0] => 3
    [1] => a
    [2] => 1
    [3] => 0
    [4] => 7
)
Array
(
    [0] => 5
    [1] => 6
    [2] => 3
    [3] => a
    [4] => 1
    [5] => 0
    [6] => 7
)
Array
(
    [0] => 5
    [1] => 6
    [2] => 3
    [3] => a
    [4] => 1
    [5] => 0
    [6] => 7
)

That means, that getting randomly array-size number of keys from the array array_rand($a, count($a)) give exactly same result as getting all the keys in order array_keys($a).

Friday, January 22, 2010

PHP: Array Pointer in Undefined State

Array pointer in PHP may become "undefined" and that is when you try to move it after the last of before the first element. But what is more surprising - copying an array remaining in that state... resets the pointer.

First let's create a messy array and see how it dumps:

$a = array (5 => 'key 5', 'key after 5', 3 => 'key 3', 'a' => 'key a', 1 => 'key 1', 0 => 'key 0', 'key after 0',);
var_dump($a);

The result (as expected):

array(7) {
  [5]=>
  string(5) "key 5"
  [6]=>
  string(11) "key after 5"
  [3]=>
  string(5) "key 3"
  ["a"]=>
  string(5) "key a"
  [1]=>
  string(5) "key 1"
  [0]=>
  string(5) "key 0"
  [7]=>
  string(11) "key after 0"
}

When Array Pointer Becomes Undefined

Let's try and go beyond the last element in the array:

// move the pointer to the last element
end($a);

// move the pointer one element further
next($a);

// ... and see where we are
var_dump(key($a), current($a));

// try to go back
prev($a);

// ... and see where we are
var_dump(key($a), current($a));

The above code will produce the following result:

NULL
bool(false)

NULL
bool(false)

Which simply means that the array pointer becomes "undefined" if moved after last element. In fact the pointer is not "after" the last element because you cannot move it back with

prev()
function. It simply remains in undefined state.

Copying Array Resets The Pointer

When you copy an array, the array pointer is copied as well and it points to the same element as in the original. But what happens when the pointer is undefined? Will the copy inherit that as well? Let's consider the code:

// copy the array
$b = $a;

// check state of the original
print ('original: key() and current()' . PHP_EOL);
var_dump(key($a), current($a));

// check state of the copy
print ('copy: key() and current()' . PHP_EOL);
var_dump(key($b), current($b));

And the result:

original: key() and current()
int(5)
string(5) "key 5"

copy: key() and current()
NULL
bool(false)

Just as anticipated, the copy has its pointer in undefined state. But what happened to the original? Apparently copying the array worked like

reset()
and moved the pointer to the first element! Needless to say, copying the array preserves the pointer's value if it's defined:

// move the pointer to the last element
end($a);

// move the pointer to one element before last
prev($a);

// see where we are
print ('key() and current()' . PHP_EOL);
var_dump(key($a), current($a));

// copy the array
$b = $a;

// check state of the original
print ('original: key() and current()' . PHP_EOL);
var_dump(key($a), current($a));

// check state of the copy
print ('copy: key() and current()' . PHP_EOL);
var_dump(key($b), current($b));

And the output:

key() and current()
int(0)
string(5) "key 0"

original: key() and current()
int(0)
string(5) "key 0"

copy: key() and current()
int(0)
string(5) "key 0"

Moving Array Pointer Before First Element

All the above applies to the situation when you move the pointer "before" the first element. But don't take my word for it - try it out.

Friday, January 15, 2010

PHP5: Bitwise Left Shift Goes in Cycle

Going through ZEND PHP Certification Study Guide I came across a bit confusing example. It says that bitwise left shift by 32 positions on integer value of 1 performed on 32-bit machine would give 0 as a result.


Reading through the guide I usually write small snippets of code to check things myself and remember them better. So I did this time. To my big surprise, the result was different from what I had read. Here is the code:

<?php
$x = 1;
echo $x << 32;
// outputs 1

I wanted to find out what was going on, so I tried:

<?php
$x = 1;
print ('PHP_INT_SIZE: ' . PHP_INT_SIZE . "\n");
$maxBits = PHP_INT_SIZE * 8;

$format = '%0' . $maxBits . "b\n";

for ($i = $maxBits - 1; $i <= $maxBits + 1; $i++ ) {
printf('x << ' . $i . ': ' . $format, $x << $i);
}

for ($i = $maxBits * 2 - 1; $i <= $maxBits * 2 + 1; $i++ ) {
printf('x << ' . $i . ': ' . $format, $x << $i);
}

And here is the result I got:

PHP_INT_SIZE: 4
x << 31: 10000000000000000000000000000000
x << 32: 00000000000000000000000000000001
x << 33: 00000000000000000000000000000010
x << 63: 10000000000000000000000000000000
x << 64: 00000000000000000000000000000001
x << 65: 00000000000000000000000000000010

That means that bitwise shifting left goes in cycle modulo architecture-defined-number-of-bits. On 32-bit machine shift left by 32 bits gives same result as no shift at all (32 % 32 = 0). Shift by 33 is same as shift by 1 (33 % 32 = 1).
But is it really true? How about shifting different value than 1? I tried to shift 2 and here is the result:

PHP_INT_SIZE: 4
x << 31: 00000000000000000000000000000000
x << 32: 00000000000000000000000000000010
x << 33: 00000000000000000000000000000100
x << 63: 00000000000000000000000000000000
x << 64: 00000000000000000000000000000010
x << 65: 00000000000000000000000000000100

I checked the results on Mac (PHP 5.2.6), Windows (PHP 5.3) and Linux (PHP 5.2.12) and they were identical. It looks like new language feature which wasn't present while the ZEND PHP Certification Study Guide has been created. Although the Bitwise Operators manual page warns: "Don't left shift in case it results to number longer than 32 bits."
It's for another discussion whether to use it or not. Anyway, I guess it's good to know.