# -*- php -*-
# The examples are taken from the Perl Cookbook
# By Tom Christiansen & Nathan Torkington
# see http://www.oreilly.com/catalog/cookbook for more

# @@PLEAC@@_NAME
# @@SKIP@@ PHP

# @@PLEAC@@_WEB
# @@SKIP@@ http://php.net/

# @@PLEAC@@_1.0
#-----------------------------
$string = '\n';                     # two characters, \ and an n
$string = 'Jon \'Maddog\' Orwant';  # literal single quotes
$string = 'Jon "Maddog" Orwant';    # literal double quotes
#-----------------------------
$string = "\n";                     # a "newline" character
$string = "Jon \"Maddog\" Orwant";  # literal double quotes
$string = "Jon 'Maddog' Orwant";    # literal single quotes
#-----------------------------
$a = 
"This is a multiline
here document";

$a = <<<EOF
This is a multiline here document
terminated by EOF on a line by itself
EOF;
#-----------------------------

# @@PLEAC@@_1.1
#-----------------------------
$value = substr($string, $offset, $count);
$value = substr($string, $offset);

$string = substr_replace($string, $newstring, $offset, $count);
$string = substr_replace($string, $newtail, $offset);
#-----------------------------
# get a 5-byte string, skip 3, then grab 2 8-byte strings, then the rest
list($leading, $s1, $s2, $trailing) =
    array_values(unpack("A5a/x3/A8b/A8c/A*d", $data);

# split at five byte boundaries
preg_match_all ("/.{5}/", $data, $f, PREG_PATTERN_ORDER);
$fivers = $f[0];

# chop string into individual characters
$chars  = $string;
#-----------------------------
$string = "This is what you have";
#         +012345678901234567890  Indexing forwards  (left to right)
#          109876543210987654321- Indexing backwards (right to left)
#           note that 0 means 10 or 20, etc. above

$first  = substr($string, 0, 1);  # "T"
$start  = substr($string, 5, 2);  # "is"
$rest   = substr($string, 13);    # "you have"
$last   = substr($string, -1);    # "e"
$end    = substr($string, -4);    # "have"
$piece  = substr($string, -8, 3); # "you"
#-----------------------------
$string = "This is what you have";
print $string;
#This is what you have

$string = substr_replace($string, "wasn't", 5, 2);  # change "is" to "wasn't"
#This wasn't what you have

$string = substr_replace($string, "ondrous", -12);  # "This wasn't wondrous"
#This wasn't wondrous

$string = substr_replace($string, "", 0, 1);        # delete first character
#his wasn't wondrous

$string = substr_replace($string, "", -10);         # delete last 10 characters
#his wasn'
#-----------------------------
if (preg_match("/pattern/", substr($string, -10)) {
    print "Pattern matches in last 10 characters\n";
}

# substitute "at" for "is", restricted to first five characters
$string=(substr_replace(preg_replace("/is/", "at", substr($string,0,5)),0,5);
#-----------------------------
# exchange the first and last letters in a string
$a = "make a hat";
list($a[0], $a[strlen($a)-1]) = Array(substr($a,-1), substr($a,0,1));
print $a;

#-----------------------------
# extract column with unpack
$a = "To be or not to be";
$b = unpack("x6/A6a", $a);  # skip 6, grab 6
print $b['a'];


$b = unpack("x6/A2b/X5/A2c", $a); # forward 6, grab 2; backward 5, grab 2
print $b['b']."\n".$b['c']."\n";

#-----------------------------
function cut2fmt() {
    $positions = func_get_args();
    $template  = '';
    $lastpos   = 1;
    foreach($positions as $place) {
        $template .= "A" . ($place - $lastpos) . " ";
        $lastpos   = $place;
    }
    $template .= "A*";
    return $template;
}

$fmt = cut2fmt(8, 14, 20, 26, 30);
print "$fmt\n";
#A7 A6 A6 A6 A4 A*
#-----------------------------

# @@PLEAC@@_1.2
#-----------------------------
# use $b if $b is true, else $c
$a = $b?$b:$c;

# set $x to $y unless $x is already true
$x || $x=$y;
#-----------------------------
# use $b if $b is defined, else $c
$a = defined($b) ? $b : $c;
#-----------------------------
$foo = $bar || $foo = "DEFAULT VALUE";
#-----------------------------
$dir = array_shift($_SERVER['argv']) || $dir = "/tmp";
#-----------------------------
$dir = $_SERVER['argv'][0] || $dir = "/tmp";
#-----------------------------
$dir = defined($_SERVER['argv'][0]) ? array_shift($_SERVER['argv']) : "/tmp";
#-----------------------------
$dir = count($_SERVER['argv']) ? $_SERVER['argv'][0] : "/tmp";
#-----------------------------
$count[$shell?$shell:"/bin/sh"]++;
#-----------------------------
# find the user name on Unix systems
$user = $_ENV['USER']
     || $user = $_ENV['LOGNAME']
     || $user = posix_getlogin()
     || $user = posix_getpwuid(posix_getuid())[0]
     || $user = "Unknown uid number $<";
#-----------------------------
$starting_point || $starting_point = "Greenwich";
#-----------------------------
count($a) || $a = $b;          # copy only if empty
$a = count($b) ? $b : $c;          # assign @b if nonempty, else @c
#-----------------------------

# @@PLEAC@@_1.3
#-----------------------------
list($VAR1, $VAR2) = array($VAR2, $VAR1);
#-----------------------------
$temp    = $a;
$a       = $b;
$b       = $temp;
#-----------------------------
$a       = "alpha";
$b       = "omega";
list($a, $b) = array($b, $a);        # the first shall be last -- and versa vice
#-----------------------------
list($alpha, $beta, $production) = Array("January","March","August");
# move beta       to alpha,
# move production to beta,
# move alpha      to production
list($alpha, $beta, $production) = array($beta, $production, $alpha);
#-----------------------------

# @@PLEAC@@_1.4
#-----------------------------
$num  = ord($char);
$char = chr($num);
#-----------------------------
$char = sprintf("%c", $num);                # slower than chr($num)
printf("Number %d is character %c\n", $num, $num);
#-----------------------------
$ASCII = unpack("C*", $string);
eval('$STRING = pack("C*", '.implode(',',$ASCII).');');
#-----------------------------
$ascii_value = ord("e");    # now 101
$character   = chr(101);    # now "e"
#-----------------------------
printf("Number %d is character %c\n", 101, 101);
#-----------------------------
$ascii_character_numbers = unpack("C*", "sample");
print explode(" ",$ascii_character_numbers)."\n";

eval('$word = pack("C*", '.implode(',',$ascii_character_numbers).');');
$word = pack("C*", 115, 97, 109, 112, 108, 101);   # same
print "$word\n";
#-----------------------------
$hal = "HAL";
$ascii = unpack("C*", $hal);
foreach ($ascii as $val) {
    $val++;                 # add one to each ASCII value
}
eval('$ibm = pack("C*", '.implode(',',$ascii).');');
print "$ibm\n";             # prints "IBM"
#-----------------------------

# @@PLEAC@@_1.5
#-----------------------------
// using perl regexp
$array = preg_split('//', $string ,-1, PREG_SPLIT_NO_EMPTY);
// using PHP function: $array = str_split($string);

// Cannot use unpack with a format of 'U*' in PHP.
#-----------------------------
for ($offset = 0; preg_match('/(.)/', $string, $matches, 0, $offset) > 0; $offset++) {
    // $matches[1] has charcter, ord($matches[1]) its number
}
#-----------------------------
$seen = array();
$string = "an apple a day";
foreach (str_split($string) as $char) {
    $seen[$char] = 1;
}
$keys = array_keys($seen);
sort($keys);
print "unique chars are: " . implode('', $keys)) . "\n";
unique chars are:  adelnpy
#-----------------------------
$seen = array();
$string = "an apple a day";
for ($offset = 0; preg_match('/(.)/', $string, $matches, 0, $offset) > 0; $offset++) {
    $seen[$matches[1]] = 1;
}
$keys = array_keys($seen);
sort($keys);
print "unique chars are: " . implode('', $keys) . "\n";
unique chars are:  adelnpy
#-----------------------------
$sum = 0;
foreach (unpack("C*", $string) as $byteval) {
    $sum += $byteval;
}
print "sum is $sum\n";
// prints "1248" if $string was "an apple a day"
#-----------------------------
$sum = array_sum(unpack("C*", $string));
#-----------------------------

// sum - compute 16-bit checksum of all input files
$handle = @fopen($argv[1], 'r');
$checksum = 0;
while (!feof($handle)) {
    $checksum += (array_sum(unpack("C*", fgets($handle))));
}
$checksum %= pow(2,16) - 1;
print "$checksum\n";

# download the following standalone program
#!/usr/bin/php
<?php
// slowcat - emulate a   s l o w   line printer

// usage: php slowcat.php [-DELAY] file

$delay = 1;
if (preg_match('/(.)/', $argv[1], $matches)) {
    $delay = $matches[1];
    array_shift($argv);
};
$handle = @fopen($argv[1], 'r');
while (!feof($handle)) {
    foreach (str_split(fgets($handle)) as $char) {
        print $char;
        usleep(5000 * $delay);
    }
}
#-----------------------------

# @@PLEAC@@_1.6
#-----------------------------
$revchars = strrev($string);
#-----------------------------
$revwords = implode(" ", array_reverse(explode(" ", $string)));
#-----------------------------
// reverse word order
$string = 'Yoda said, "can you see this?"';
$allwords    = explode(" ", $string);
$revwords    = implode(" ", array_reverse($allwords));
print $revwords . "\n";
this?" see you "can said, Yoda
#-----------------------------
$revwords = implode(" ", array_reverse(explode(" ", $string)));
#-----------------------------
$revwords = implode(" ", array_reverse(preg_split("/(\s+)/", $string)));
#-----------------------------
$word = "reviver";
$is_palindrome = ($word === strrev($word));
#-----------------------------
// quite a one-liner since "php" does not have a -n switch
% php -r 'while (!feof(STDIN)) { $word = rtrim(fgets(STDIN)); if ($word == strrev($word) && strlen($word) > 5) print $word; }' < /usr/dict/words
#-----------------------------

# @@PLEAC@@_1.8
#-----------------------------
$text = preg_replace('/\$(\w+)/e', '$$1', $text);
#-----------------------------
list($rows, $cols) = Array(24, 80);
$text = 'I am $rows high and $cols long';
$text = preg_replace('/\$(\w+)/e', '$$1', $text);
print $text;

#-----------------------------
$text = "I am 17 years old";
$text = preg_replace('/(\d+)/e', '2*$1', $text);
#-----------------------------
# expand variables in $text, but put an error message in
# if the variable isn't defined
$text = preg_replace('/\$(\w+)/e','isset($$1)?$$1:\'[NO VARIABLE: $$1]\'', $text);
#-----------------------------

// As PHP arrays are used as hashes too, separation of section 4
// and section 5 makes little sense.

# @@PLEAC@@_1.9
#-----------------------------
$big = strtoupper($little);
$little = strtolower($big);
// PHP does not have the\L and\U string escapes.
#-----------------------------
$big = ucfirst($little);
$little = strtolower(substr($big, 0, 1)) . substr($big, 1);
#-----------------------------
$beast   = "dromedary";
// capitalize various parts of $beast
$capit   = ucfirst($beast); // Dromedar
// PHP does not have the\L and\U string escapes.
$capall  = strtoupper($beast); // DROMEDAR
// PHP does not have the\L and\U string escapes.
$caprest = strtolower(substr($beast, 0, 1)) . substr(strtoupper($beast), 1); // dROMEDAR
// PHP does not have the\L and\U string escapes.
#-----------------------------
// titlecase each word's first character, lowercase the rest
$text = "thIS is a loNG liNE";
$text = ucwords(strtolower($text));
print $text;
This Is A Long Line
#-----------------------------
if (strtoupper($a) == strtoupper($b)) { // or strcasecmp($a, $b) == 0
    print "a and b are the same\n";
}
#-----------------------------
# download the following standalone program
#!/usr/bin/php
<?php
// randcap: filter to randomly capitalize 20% of the letters

function randcase($word) {
  return rand(0, 100) < 20 ? ucfirst($word) : lcfirst($word);
}
function lcfirst($word) {
  return strtolower(substr($word, 0, 1)) . substr($word, 1);
}
while (!feof(STDIN)) {
  print preg_replace("/(\w)/e", "randcase('\\1')", fgets(STDIN));
}

// % php randcap.php < genesis | head -9
#-----------------------------

# @@PLEAC@@_1.10
#-----------------------------
echo $var1 . func() . $var2; // scalar only
#-----------------------------
// PHP can only handle variable expression without operators
$answer = "STRING ${[ VAR EXPR ]} MORE STRING";
#-----------------------------
$phrase = "I have " . ($n + 1) . " guanacos.";
// PHP cannot handle the complex exression: ${\($n + 1)}
#-----------------------------
// Rest of Discussion is not applicable to PHP
#-----------------------------
// Interpolating functions not available in PHP
#-----------------------------

# @@PLEAC@@_1.11
# @@INCOMPLETE@@
# @@INCOMPLETE@@

# @@PLEAC@@_1.12
#-----------------------------
$output = wordwrap($str, $width, $break, $cut);
#-----------------------------
# download the following standalone program
#!/usr/bin/php
<?php
// wrapdemo - show how wordwrap works

$input = "Folding and splicing is the work of an editor, " .
         "not a mere collection of silicon " .
         "and " .
         "mobile electrons!";
$columns = 20;
print str_repeat("0123456789", 2) . "\n";
print wordwrap('    ' . $input, $columns - 3, "\n  ") . "\n";
#-----------------------------
// merge multiple lines into one, then wrap one long line
print wordwrap(str_replace("\n", " ", file_get_contents('php://stdin')));
#-----------------------------
while(!feof(STDIN)) {
    print wordwrap(str_replace("\n", " ", stream_get_line(STDIN, 0, "\n\n")));
    print "\n\n";
}
#-----------------------------

# @@PLEAC@@_1.13
#-----------------------------
//backslash
$var = preg_replace('/([CHARLIST])/', '\\\$1', $var);
// double
$var = preg_replace('/([CHARLIST])/', '$1$1', $var);
#-----------------------------
$var = preg_replace('/%/', '%%', $var);
#-----------------------------
$string = 'Mom said, "Don\'t do that."';
$string = preg_replace('/([\'"])/', '\\\$1', $string);
// in PHP you can also use the addslashes() function
#-----------------------------
$string = 'Mom said, "Don\'t do that."';
$string = preg_replace('/([\'"])/', '$1$1', $string);
#-----------------------------
$string = preg_replace('/([^A-Z])/', '\\\$1', $string);
#-----------------------------
// PHP does not have the \Q and \E string metacharacters
$string = "this is\\ a\\ test\\!";
// PHP's quotemeta() function is not the same as perl's quotemeta() function
$string = preg_replace('/(\W)/', '\\\$1', 'is a test!');
#-----------------------------

# @@PLEAC@@_1.14
#-----------------------------
$string = trim($string);
#-----------------------------
// print what's typed, but surrounded by > < symbols
while (!feof(STDIN)) {
    print ">" . substr(fgets(STDIN), 0, -1) . "<\n";
}
#-----------------------------
$string = preg_replace('/\s+/', ' ', $string); // finally, collapse middle
#-----------------------------
$string = trim($string);
$string = preg_replace('/\s+/', ' ', $string);
#-----------------------------
// 1. trim leading and trailing white space
// 2. collapse internal whitespace to single space each
function sub_trim($string) {
    $string = trim($string);
    $string = preg_replace('/\s+/', ' ', $string);
    return $string;
}
#-----------------------------

# @@PLEAC@@_1.15
# @@INCOMPLETE@@
# @@INCOMPLETE@@

# @@PLEAC@@_1.16
#-----------------------------
$code = soundex($string);
#-----------------------------
$phoned_words = metaphone("Schwern");
#-----------------------------
// substitution function for getpwent():
// returns an array of user entries,
// each entry contains the username and the full name
function getpwent() {
    $pwents = array();
    $handle = fopen("passwd", "r");
    while (!feof($handle)) {
        $line = fgets($handle);
        if (preg_match("/^#/", $line)) continue;
        $cols = explode(":", $line);
        $pwents[$cols[0]] = $cols[4];
    }
    return $pwents;
}

print "Lookup user: ";
$user = rtrim(fgets(STDIN));
if (empty($user)) exit;
$name_code = soundex($user);
$pwents = getpwent();
foreach($pwents as $username => $fullname) {
    preg_match("/(\w+)[^,]*\b(\w+)/", $fullname, $matches);
    list(, $firstname, $lastname) = $matches;
  
    if ($name_code == soundex($username) ||
        $name_code == soundex($lastname) ||
        $name_code == soundex($firstname))
    {
        printf("%s: %s %s\n", $username, $firstname, $lastname);
    }
}
#-----------------------------

# @@PLEAC@@_1.17
#-----------------------------
# download the following standalone program
#!/usr/bin/php
<?php
$data = <<<DATA
  analysed=> analyzed
  built-in=> builtin
  chastized   => chastised
  commandline => command-line
  de-allocate => deallocate
  dropin  => drop-in
  hardcode=> hard-code
  meta-data   => metadata
  multicharacter  => multi-character
  multiway=> multi-way
  non-empty   => nonempty
  non-profit  => nonprofit
  non-trappable   => nontrappable
  pre-define  => predefine
  preextend   => pre-extend
  re-compiling=> recompiling
  reenter => re-enter
  turnkey => turn-key
DATA;

$scriptName = $argv[0];
$verbose = ($argc > 1 && $argv[1] == "-v" && array_shift($argv));
$change = array();
foreach (preg_split("/\n/", $data) as $pair) {
  list($in, $out) = preg_split("/\s*=>\s*/", trim($pair));
  if (!$in || !$out) continue;
  $change[$in] = $out;
}
if (count($argv) > 1)  {
  // no in-place edit in PHP

  // preserve old files

  $orig = $argv[1] . ".orig";
  copy($argv[1], $orig);
  $input = fopen($orig, "r");
  $output = fopen($argv[1], "w");
} else if ($scriptName != "-") {
  $input = STDIN;
  trigger_error("$scriptName: Reading from stdin\n", E_USER_WARNING);
}
$ln = 1;
while (!feof($input)) {
  $line = fgets($input);
  foreach ($change as $in => $out) {
    $line = preg_replace("/$in/", $out, $line, -1, $count);
    if ($count > 0 && $verbose) {
      fwrite(STDERR, "$in => $out at $argv[1] line $ln.\n");
    }
  }
  @fwrite($output, $line);
  $ln++;
}
#-----------------------------
# download the following standalone program
#!/usr/bin/php
<?php
$data = <<<DATA
  analysed=> analyzed
  built-in=> builtin
  chastized   => chastised
  commandline => command-line
  de-allocate => deallocate
  dropin  => drop-in
  hardcode=> hard-code
  meta-data   => metadata
  multicharacter  => multi-character
  multiway=> multi-way
  non-empty   => nonempty
  non-profit  => nonprofit
  non-trappable   => nontrappable
  pre-define  => predefine
  preextend   => pre-extend
  re-compiling=> recompiling
  reenter => re-enter
  turnkey => turn-key
DATA;

$scriptName = $argv[0];
$verbose = ($argc > 1 && $argv[1] == "-v" && array_shift($argv));
if (count($argv) > 1)  {
  // no in-place edit in PHP

  // preserve old files

  $orig = $argv[1] . ".orig";
  copy($argv[1], $orig);
  $input = fopen($orig, "r");
  $output = fopen($argv[1], "w");
} else if ($scriptName != "-") {
  $input = STDIN;
  trigger_error("$scriptName: Reading from stdin\n", E_USER_WARNING);
}

$config = array();
foreach (preg_split("/\n/", $data) as $pair) {
  list($in, $out) = preg_split("/\s*=>\s*/", trim($pair));
  if (!$in || !$out) continue;
  $config[$in] = $out;
}

$ln = 1;
while (!feof($input)) {
  $i = 0;
  preg_match("/^(\s*)(.*)/", fgets($input), $matches); // emit leading whitespace

  fwrite($output, $matches[1]);
  foreach (preg_split("/(\s+)/", $matches[2], -1, PREG_SPLIT_DELIM_CAPTURE) as $token) { // preserve trailing whitespace

    fwrite($output, ($i++ & 1) ? $token : (array_key_exists($token, $config) ? $config[$token] : $token));
  }
}
#-----------------------------
// very fast, but whitespace collapse
while (!feof($input)) {
  $i = 0;
  preg_match("/^(\s*)(.*)/", fgets($input), $matches); // emit leading whitespace
  fwrite($output, $matches[1]);
  foreach (preg_split("/(\s+)/", $matches[2]) as $token) { // preserve trailing whitespace
    fwrite($output, (array_key_exists($token, $config) ? $config[$token] : $token) . " ");
  }
  fwrite($output, "\n");
}
#-----------------------------

// @@PLEAC@@_2.0
// As is the case under so many other languages floating point use under PHP is fraught
// with dangers. Although the basic techniques shown below are valid, please refer to
// the official PHP documentation for known issues, bugs, and alternate approaches 

// @@PLEAC@@_2.1
// Two basic approaches to numeric validation:
// * Built-in functions like 'is_numeric', 'is_int', 'is_float' etc
// * Regexes, as shown below

$s = '12.345';

preg_match('/\D/', $s) && die("has nondigits\n");
preg_match('/^\d+$/', $s) || die("not a natural number\n");
preg_match('/^-?\d+$/', $s) || die("not an integer\n");
preg_match('/^[+-]?\d+$/', $s) || die("not an integer\n");
preg_match('/^-?\d+\.?\d*$/', $s) || die("not a decimal\n");
preg_match('/^-?(?:\d+(?:\.\d*)?|\.\d+)$/', $s) || die("not a decimal\n");
preg_match('/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/', $s) || die("not a C float\n");

// ----------------------------

function getnum($s)
{
  sscanf($s, "%D", $number); return isset($number) ? $number : 0;
}

echo getnum(123) . "\n";   // ok
echo getnum(0xff) . "\n";  // ..
echo getnum(044) . "\n";   // ..

echo getnum('x') . "\n";   // fail

// @@PLEAC@@_2.2
// In PHP floating point comparisions are 'safe' [meaning the '==' comparison operator
// can be used] as long as the value consists of 14 digits or less [total digits, either
// side of the decimal point e.g. xxxxxxx.xxxxxxx, xxxxxxxxxxxxxx., .xxxxxxxxxxxxxx]. If
// values with more digits must be compared, then:
//
// * Represent as strings, and take care to avoid any implicit conversions e.g. don't pass
//   a float as a float to a function and expect all digits to be retained - they won't be -
//   then use 'strcmp' to compare the strings
//
// * Avoid float use; perhaps use arbitrary precision arithmetic. In this case, the
//   'bccomp' function is relevant

// Will work as long as each floating point value is 14 digits or less
if ($float_1 == $float_2)
{
  ; // ...
}

// Compare as strings
$cmp = strcmp('123456789.123456789123456789', '123456789.123456789123456788');

// Use 'bccomp'
$precision = 5; // Number of significant comparison digits after decimal point
if (bccomp('1.111117', '1.111116', $precision))
{
  ; // ...
}

$precision = 6;
if (bccomp('1.111117', '1.111116', $precision))
{
  ; // ...
}

// ----------------------------

$wage = 536;
$week = $wage * 40;
printf("One week's wage is: $%.2f\n", $week / 100);

// @@PLEAC@@_2.3
// Preferred approach
$rounded = round($unrounded, $precision);

// Possible alternate approach
$format = '%[width].[prec]f';
$rounded = sprintf($format, $unrounded);

// ------------

$a = 0.255; $b = round($a, 2);
echo "Unrounded: {$a}\nRounded: {$b}\n";

$a = 0.255; $b = sprintf('%.2f', $a);
echo "Unrounded: {$a}\nRounded: {$b}\n";

$a = 0.255;
printf("Unrounded: %.f\nRounded: %.2f\n", $a, $a);

// ----------------------------

echo "number\tint\tfloor\tceil\n";

foreach(array(3.3, 3.5, 3.7, -3.3) as $number)
{
  printf("%.1f\t%.1f\t%.1f\t%.1f\n", $number, (int) $number, floor($number), ceil($number));
}

// @@PLEAC@@_2.4
// PHP offers the 'bindec' and 'decbin' functions to converting between binary and decimal

$num = bindec('0110110');

$binstr = decbin(54);

// @@PLEAC@@_2.5
foreach (range($X, $Y) as $i)
{
  ; // ...
}

foreach (range($X, $Y, 7) as $i)
{
  ; // ...
}

for ($i = $X; $i <= $Y; $i++)
{
  ; // ...
}

for ($i = $X; $i <= $Y; $i += 7)
{
  ; // ...
}

// ----------------------------

echo 'Infancy is:'; foreach(range(0, 2) as $i) echo " {$i}\n";
echo 'Toddling is:'; foreach(range(3, 4) as $i) echo " {$i}\n";
echo 'Childhood is:'; foreach(range(5, 12) as $i) echo " {$i}\n";

// @@PLEAC@@_2.6
// PHP offers no native support for Roman Numerals. However, a 'Numbers_Roman' class
// is available for download from PEAR: [http://pear.php.net/package/Numbers_Roman].
// Note the following 'include' directives are required:
//
//   include_once('Numbers/Roman.php');

$roman = Numbers_Roman::toNumeral($arabic);
$arabic = Numbers_Roman::toNumber($roman);

// ----------------------------

$roman_fifteen = Numbers_Roman::toNumeral(15);

$arabic_fifteen = Numbers_Roman::toNumber($roman_fifteen);

printf("Roman for fifteen is: %s\n", $roman_fifteen);
printf("Arabic for fifteen is: %d\n", $arabic_fifteen);

// @@PLEAC@@_2.7
// Techniques used here simply mirror Perl examples, and are not an endorsement
// of any particular RNG technique

// In PHP do this ...
$random = rand($lowerbound, $upperbound);
$random = rand($x, $y);

// ----------------------------

function make_password($chars, $reqlen)
{
  $len = strlen($chars);
  for ($i = 0; $i < $reqlen; $i++) $password .= substr($chars, rand(0, $len), 1);
  return $password;
}

$chars = 'ABCDEfghijKLMNOpqrstUVWXYz'; $reqlen = 8;

$password = make_password($chars, $reqlen);

// @@PLEAC@@_2.8
// PHP sports a large number of C Standard Library routines including the 'srand'
// function, used to re-seed the RNG used with calls to the 'rand' function. Thus,
// as per Perl example:

while (TRUE)
{
  $seed = (int) fgets(STDIN);
  if (!empty($seed)) break;
}

srand($seed);

// @@PLEAC@@_2.9
// The above is considered - for many reasons - a poor way of seeding the RNG. PHP
// also offers alternate versions of the functions, 'mt_srand' and 'mt_rand',
// which are described as faster, and more 'random', though key to obtaining a
// more 'random' distribution of generated numbers seems to be through using
// a combination of a previously saved random value in combination with an
// unrepeatable value [like the current time in microseconds] that is multiplied
// by a large prime number, or perhaps as part of a hash [examples available in
// PHP documentation for 'srand' and 'mt_srand']

mt_srand($saved_random_value + microtime() * 1000003);

// or

mt_srand(($saved_random_value + hexdec(substr(md5(microtime()), -8))) & 0x7fffffff);

// Use of 'mt_rand' together with an appropriate seeding approach should help better
// approximate the generation of a 'truly random value'
$truly_random_value = mt_rand();

// @@PLEAC@@_2.10
function random() { return (float) rand() / (float) getrandmax(); }

function gaussian_rand()
{
  $u1 = 0.0; $u2 = 0.0; $g1 = 0.0; $g2 = 0.0; $w = 0.0;
  
  do
  {
    $u1 = 2.0 * random() - 1.0; $u2 = 2.0 * random() - 1.0;
    $w = $u1 * $u1 + $u2 * $u2;
  } while ($w > 1.0);
  
  $w = sqrt((-2.0 * log($w)) / $w); $g2 = $u1 * $w; $g1 = $u2 * $w;

  return $g1;
}

// ------------

$mean = 25.0; $sdev = 2.0;
$salary = gaussian_rand() * $mean + $sdev;

printf("You have been hired at: %.2f\n", $salary);

// @@PLEAC@@_2.11
// 'deg2rad' and 'rad2deg' are actually PHP built-ins, but here is how you might implement
/  them if needed
function deg2rad_($deg) { return ($deg / 180.0) * M_PI; }
function rad2deg_($rad) { return ($rad / M_PI) * 180.0; }

// ------------

printf("%f\n", deg2rad_(180.0));
printf("%f\n", deg2rad(180.0));

// ----------------------------

function degree_sin($deg) { return sin(deg2rad($deg)); }

// ------------

$rad = deg2rad(380.0);

printf("%f\n", sin($rad));
printf("%f\n", degree_sin(380.0));

// @@PLEAC@@_2.12
function my_tan($theta) { return sin($theta) / cos($theta); }

// ------------

$theta = 3.7;

printf("%f\n", my_tan($theta));
printf("%f\n", tan($theta));

// @@PLEAC@@_2.13
$value = 100.0;

$log_e = log($value);
$log_10 = log10($value);

// ----------------------------

function log_base($base, $value) { return log($value) / log($base); }

// ------------

$answer = log_base(10.0, 10000.0);

printf("log(10, 10,000) = %f\n", $answer);

// @@PLEAC@@_2.14
// PHP offers no native support for matrices. However, a 'Math_Matrix' class
// is available for download from PEAR: [http://pear.php.net/package/Math_Matrix].
// Note the following 'include' directives are required:
//
//  include_once('Math/Matrix.php');

$a = new Math_Matrix(array(array(3, 2, 3), array(5, 9, 8)));
$b = new Math_Matrix(array(array(4, 7), array(9, 3), array(8, 1)));

echo $a->toString() . "\n";
echo $b->toString() . "\n";

// NOTE: When I installed this package I had to rename the 'clone' method else
// it would not load, so I chose to rename it to 'clone_', and this usage is
// shown below. This bug may well be fixed by the time you obtain this package

$c = $a->clone_();
$c->multiply($b);

echo $c->toString() . "\n";

// @@PLEAC@@_2.15
// PHP offers no native support for complex numbers. However, a 'Math_Complex' class
// is available for download from PEAR: [http://pear.php.net/package/Math_Complex].
// Note the following 'include' directives are required:
//
//   include_once('Math/Complex.php');
//   include_once('Math/TrigOp.php');
//   include_once('Math/ComplexOp.php');

$a = new Math_Complex(3, 5);
$b = new Math_Complex(2, -2);

$c = Math_ComplexOp::mult($a, $b);

echo $c->toString() . "\n";

// ----------------------------

$d = new Math_Complex(3, 4);
$r = Math_ComplexOp::sqrt($d);

echo $r->toString() . "\n";

// @@PLEAC@@_2.16
// Like C, PHP supports decimal-alternate notations. Thus, for example, the integer
// value, 867, is expressable in literal form as:
//
//   Hexadecimal -> 0x363
//   Octal       -> 01543
//
// For effecting such conversions using strings there is 'sprintf' and 'sscanf'.

$dec = 867;
$hex = sprintf('%x', $dec);
$oct = sprintf('%o', $dec);

// ------------

$dec = 0;
$hex = '363';

sscanf($hex, '%x', $dec);

// ------------

$dec = 0;
$oct = '1543';

sscanf($oct, '%o', $dec);

// ----------------------------

$number = 0;

printf('Gimme a number in decimal, octal, or hex: ');
sscanf(fgets(STDIN), '%D', $number);

printf("%d %x %o\n", $number, $number, $number);

// @@PLEAC@@_2.17
// PHP offers the 'number_format' built-in function to, among many other format tasks, 
// commify numbers. Perl-compatible [as well as extended] regexes are also available

function commify_series($s) { return number_format($s, 0, '', ','); }

// ------------

$hits = 3456789;

printf("Your website received %s accesses last month\n", commify_series($hits));

// ----------------------------

function commify($s)
{
  return strrev(preg_replace('/(\d\d\d)(?=\d)(?!\d*\.)/', '${1},', strrev($s)));
}

// ------------

$hits = 3456789;

echo commify(sprintf("Your website received %d accesses last month\n", $hits));

// @@PLEAC@@_2.18
function pluralise($value, $root, $singular='' , $plural='s')
{
  return $root . (($value > 1) ? $plural : $singular);
}

// ------------

$duration = 1;
printf("It took %d %s\n", $duration, pluralise($duration, 'hour'));
printf("%d %s %s enough.\n", $duration, pluralise($duration, 'hour'),
      pluralise($duration, '', 'is', 'are'));

$duration = 5;
printf("It took %d %s\n", $duration, pluralise($duration, 'hour'));
printf("%d %s %s enough.\n", $duration, pluralise($duration, 'hour'),
      pluralise($duration, '', 'is', 'are'));

// ----------------------------

function plural($singular)
{
  $s2p = array('/ss$/' => 'sses', '/([psc]h)$/' => '${1}es', '/z$/' => 'zes',
               '/ff$/' => 'ffs', '/f$/' => 'ves', '/ey$/' => 'eys',
               '/y$/' => 'ies', '/ix$/' => 'ices', '/([sx])$/' => '$1es',
               '$' => 's');

  foreach($s2p as $s => $p)
  {
    if (preg_match($s, $singular)) return preg_replace($s, $p, $singular);
  }
}

// ------------

foreach(array('mess', 'index', 'leaf', 'puppy') as $word)
{
  printf("%6s -> %s\n", $word, plural($word));
}

// @@PLEAC@@_2.19
// @@INCOMPLETE@@
// @@INCOMPLETE@@

// @@PLEAC@@_3.0
// PHP's date / time suport is quite extensive, and appears grouped into three areas of
// functionality:
//
// * UNIX / C Library [libc]-based routines, which include [among others]:
//   - localtime, gmtime
//   - strftime, strptime, mktime
//   - time, getdate, gettimeofday, 
//
// * PHP 'native' functions, those date / time routines released in earlier versions,
//   and which otherwise provide 'convenience' functionality; these include:
//   - date
//   - strtotime
//
// * 'DateTime' class-based. This facility appears [according to the PHP documentation]
//   to be extremely new / experimental, so whilst usage examples will be provided, they
//   should not be taken to be 'official' examples, and obviously, subject to change.
//   My own impression is that this facility is currently only partially implemented,
//   so there is limited use for these functions. The functions included in this group
//   are some of the 'date_'-prefixed functions; they are, however, not used standalone,
//   but as methods in conjunction with an object. Typical usage:
//
//     $today = new DateTime();             // actually calls: date_create($today, ...);
//     echo $today->format('U') . "\n";     // actually calls: date_format($today, ...);
//
// Also worth mentioning is the PEAR [PHP Extension and Repository] package, 'Calendar',
// which offers a rich set of date / time manipulation facilities. However, since it is
// not currently shipped with PHP, no examples appear

// Helper functions for performing date arithmetic 

function dateOffset()
{
  static $tbl = array('sec' => 1, 'min' => 60, 'hou' => 3600, 'day' => 86400, 'wee' => 604800);
  $delta = 0;

  foreach (func_get_args() as $arg)
  {
    $kv = explode('=', $arg);
    $delta += $kv[1] * $tbl[strtolower(substr($kv[0], 0, 3))];
  }

  return $delta;
}

function dateInterval($intvltype, $timevalue)
{
  static $tbl = array('sec' => 1, 'min' => 60, 'hou' => 3600, 'day' => 86400, 'wee' => 604800);
  return (int) round($timevalue / $tbl[strtolower(substr($intvltype, 0, 3))]);
}

// ----------------------------

// Extract indexed array from 'getdate'
$today = getdate();
printf("Today is day %d of the current year\n", $today['yday']);

// Extract indexed, and associative arrays, respectively, from 'localtime'
$today = localtime();
printf("Today is day %d of the current year\n", $today[7]);

$today = localtime(time(), TRUE);
printf("Today is day %d of the current year\n", $today['tm_yday']);

// @@PLEAC@@_3.1
define(SEP, '-');

// ------------

$today = getdate();

$day = $today['mday'];
$month = $today['mon'];
$year = $today['year'];

// Either do this to use interpolation:
$sep = SEP;
echo "Current date is: {$year}{$sep}{$month}{$sep}{$day}\n";

// or simply concatenate:
echo 'Current date is: ' . $year . SEP . $month . SEP . $day . "\n";

// ------------

$today = localtime(time(), TRUE);

$day = $today['tm_mday'];
$month = $today['tm_mon'] + 1;
$year = $today['tm_year'] + 1900;

printf("Current date is: %4d%s%2d%s%2d\n", $year, SEP, $month, SEP, $day);

// ------------

$format = 'Y' . SEP . 'n' . SEP . 'd';

$today = date($format);

echo "Current date is: {$today}\n";

// ------------

$sep = SEP;

$today = strftime("%Y$sep%m$sep%d");

echo "Current date is: {$today}\n";

// @@PLEAC@@_3.2
$timestamp = mktime($hour, $min, $sec, $month, $day, $year);

$timestamp = gmmktime($hour, $min, $sec, $month, $day, $year);

// @@PLEAC@@_3.3
$dmyhms = getdate();            // timestamp: current date / time

$dmyhms = getdate($timestamp);  // timestamp: arbitrary

$day = $dmyhms['mday'];
$month = $dmyhms['mon'];
$year = $dmyhms['year'];

$hours = $dmyhms['hours'];
$minutes = $dmyhms['minutes'];
$seconds = $dmyhms['seconds'];

// @@PLEAC@@_3.4
// Date arithmetic is probably most easily performed using timestamps [i.e. *NIX Epoch
// Seconds]. Dates - in whatever form - are converted to timestamps, these are
// arithmetically manipulated, and the result converted to whatever form required.
// Note: use 'mktime' to create timestamps properly adjusted for daylight saving; whilst
// 'strtotime' is more convenient to use, it does not, AFAIK, include this adjustment

$when = $now + $difference;
$then = $now - $difference;

// ------------

$now = mktime(0, 0, 0, 8, 6, 2003);

$diff1 = dateOffset('day=1'); $diff2 = dateOffset('weeks=2');

echo 'Today is:                 ' . date('Y-m-d', $now) . "\n";
echo 'One day in the future is: ' . date('Y-m-d', $now + $diff1) . "\n";
echo 'Two weeks in the past is: ' . date('Y-m-d', $now - $diff2) . "\n";

// ----------------------------

// Date arithmetic performed using a custom function, 'dateOffset'. Internally, offset may
// be computed in one of several ways:
// * Direct timestamp manipulation - fastest, but no daylight saving adjustment 
// * Via 'date' built-in function - slower [?], needs a base time from which to
//   compute values, but has daylight saving adjustment 
// * Via 'strtotime' built-in function - as for 'date'
// * Via 'DateTime' class
//
// Approach used here is to utilise direct timestamp manipulation in 'dateOffset' [it's
// performance can also be improved by replacing $tbl with a global definition etc],
// and to illustrate how the other approaches might be used 

// 1. 'dateOffset'

$birthtime = mktime(3, 45, 50, 1, 18, 1973);

$interval = dateOffset('day=55', 'hours=2', 'min=17', 'sec=5');

$then = $birthtime + $interval;

printf("Birthtime is: %s\nthen is:      %s\n", date(DATE_RFC1123, $birthtime), date(DATE_RFC1123, $then));

// ------------

// 2. 'date'

// Base values, and offsets, respectively
$hr = 3; $min = 45; $sec = 50; $mon = 1; $day = 18; $year = 1973;

$yroff = 0; $monoff = 0; $dayoff = 55; $hroff = 2; $minoff = 17; $secoff = 5;

// Base date
$birthtime = mktime($hr, $min, $sec, $mon, $day, $year, TRUE);

$year = date('Y', $birthtime) + $yroff;
$mon = date('m', $birthtime) + $monoff;
$day = date('d', $birthtime) + $dayoff;

$hr = date('H', $birthtime) + $hroff;
$min = date('i', $birthtime) + $minoff;
$sec = date('s', $birthtime) + $secoff;

// Offset date
$then = mktime($hr, $min, $sec, $mon, $day, $year, TRUE);

printf("Birthtime is: %s\nthen is:      %s\n", date(DATE_RFC1123, $birthtime), date(DATE_RFC1123, $then));

// ------------

// 3. 'strtotime'

// Generate timestamp whatever way is preferable
$birthtime = mktime(3, 45, 50, 1, 18, 1973);
$birthtime = strtotime('1/18/1973 03:45:50');

$then = strtotime('+55 days 2 hours 17 minutes 2 seconds', $birthtime);

printf("Birthtime is: %s\nthen is:      %s\n", date(DATE_RFC1123, $birthtime), date(DATE_RFC1123, $then));

// ------------

// 4. 'DateTime' class

$birthtime = new DateTime('1/18/1973 03:45:50');
$then = new DateTime('1/18/1973 03:45:50');
$then->modify('+55 days 2 hours 17 minutes 2 seconds');

printf("Birthtime is: %s\nthen is:      %s\n", $birthtime->format(DATE_RFC1123), $then->format(DATE_RFC1123));

// @@PLEAC@@_3.5
// Date intervals are most easily computed using timestamps [i.e. *NIX Epoch
// Seconds] which, of course, gives the interval result is seconds from which
// all other interval measures [days, weeks, months, years] may be derived.
// Refer to previous section for discussion of daylight saving and other related
// problems

$interval_seconds = $recent - $earlier;

// ----------------------------

// Conventional approach ...
$bree = strtotime('16 Jun 1981, 4:35:25');
$nat = strtotime('18 Jan 1973, 3:45:50');

// ... or, with daylight saving adjustment
$bree = mktime(4, 35, 25, 6, 16, 1981, TRUE);
$nat = mktime(3, 45, 50, 1, 18, 1973, TRUE);

$difference = $bree - $nat;

// 'dateInterval' custom function computes intervals in several measures given an
// interval in seconds. Note, 'month' and 'year' measures not provided
printf("There were %d seconds between Nat and Bree\n", $difference);
printf("There were %d weeks between Nat and Bree\n", dateInterval('weeks', $difference));
printf("There were %d days between Nat and Bree\n", dateInterval('days', $difference));
printf("There were %d hours between Nat and Bree\n", dateInterval('hours', $difference));
printf("There were %d minutes between Nat and Bree\n", dateInterval('mins', $difference));

// @@PLEAC@@_3.6
// 'getdate' accepts a timestamp [or implicitly calls 'time'] and returns an array of
// date components. It returns much the same information as 'strptime' except that
// the component names are different

$today = getdate();

$weekday = $today['wday'];
$monthday = $today['mday'];
$yearday = $today['yday'];

$weeknumber = (int) round($yearday / 7.0);

// Safter method of obtaining week number
$weeknumber = strftime('%U') + 1;

// ----------------------------

define(SEP, '/');

$day = 16;
$month = 6;
$year = 1981;

$timestamp = mktime(0, 0, 0, $month, $day, $year);

$date = getdate($timestamp);

$weekday = $date['wday'];
$monthday = $date['mday'];
$yearday = $date['yday'];

$weeknumber = (int) round($yearday / 7.0);

$weeknumber = strftime('%U', $timestamp) + 1;

// Interpolate ...
$sep = SEP;
echo "{$month}{$sep}{$day}{$sep}{$year} was a {$date['weekday']} in week {$weeknumber}\n";

// ... or, concatenate
echo $month . SEP . $day . SEP . $year . ' was a ' . $date['weekday']
     . ' in week ' . $weeknumber . "\n";

// @@PLEAC@@_3.7
// 'strtotime' parses a textual date expression by attempting a 'best guess' at
// the format, and either fails, or generates a timestamp. Timestamp could be fed
// into any one of the various functions; example:
$timestamp = strtotime('1998-06-03'); echo strftime('%Y-%m-%d', $timestamp) . "\n";

// 'strptime' parses a textual date expression according to a specified format,
// and returns an array of date components; components can be easily dumped
print_r(strptime('1998-06-03', '%Y-%m-%d'));

// ----------------------------

// Parse date string according to format
$darr = strptime('1998-06-03', '%Y-%m-%d');

if (!empty($darr))
{
  // Show date components in 'debug' form
  print_r($darr);

  // Check whether there was a parse error i.e. one or more components could not
  // be extracted from the string
  if (empty($darr['unparsed']))
  {
    // Properly parsed date, so validate required components using, 'checkdate'
    if (checkdate($darr['tm_mon'] + 1, $darr['tm_mday'], $darr['tm_year'] + 1900))
      echo "Parsed date verified as correct\n";
    else
      echo "Parsed date failed verification\n";
  }
  else
  {
    echo "Date string parse not complete; failed components: {$darr['unparsed']}\n";
  }
}
else
{
  echo "Date string could not be parsed\n";
}

// @@PLEAC@@_3.8
// 'date' and 'strftime' both print a date string based on:
// * Format String, describing layout of date components
// * Timestamp [*NIX Epoch Seconds], either given explicitly, or implictly
//   via a call to 'time' which retrieves current time value

$ts = 1234567890;

date('Y/m/d', $ts); 
date('Y/m/d', mktime($h, $m, $s, $mth, $d, $y, $is_dst)); 

date('Y/m/d');         // same as: date('Y/m/d', time());

// ------------

$ts = 1234567890;

strftime('%Y/%m/%d', $ts);
strftime('%Y/%m/%d', mktime($h, $m, $s, $mth, $d, $y, $is_dst));

strftime('%Y/%m/%d');