A little tool to create SQL create statements from an XMI entity relationship diagram. Useful if you design your databases in a tool like, for example, Umbrello.
Code Share
So I'm flipping through one of my old programming texts (one of the ones in which I/O is principally done through punched cards) and come across an algorithm to find the intersection of two sets of numbers that is asymptotically faster than checking each element of one set to see if it appears in the other. (Remember through all this that the elements of a set are distinct, by definition; while array_intersect() will retain duplicate values, this won't.)
It is limited to integers only; but since these might be ID numbers for something more interesting, that's not a huge limitation.
It is noticeably faster than using array_intersect() once the sizes of the sets involved and the range of possible values climb above a couple of dozen or so. Below that, the extra overhead of having all the logic in bytecode instead of machine code dominates.
This is the algorithm. To find the intersection of two sets of integers A and B:
Multiply the values of A and B by 2
Add one to all the values of B
merge A and B into one list
sort the list (this is the bit that dominates the running time)
Walk through the sorted list; if 2n and 2n+1 are both elements of the list (if they are then, due to the sorting, they will be adjacent), then n is an element of both A and B.
And here's some code to share:
foreach($a as &$av) $av+=$av;
foreach($b as &$bv) $bv+=$bv+1;
unset($av, $bv);
$merge = array_merge($a,$b);
sort($merge);
$n = count($merge)-1;
$intersection = array();
for($i=0; $i<$n; $i++)
{
if(!($merge[$i]&1) && ($merge[$i+1]==$merge[$i]+1))
{
$intersection[] = $merge[$i++]>>1;
}
}
A potentially long chunk of text may be previewed on another page by exceprting the first n words or n characters to use as an abstract. When taking the first characters, ideally you don't want it to break off in mid-word (you don't want something containing "...she had a mandate to..." to be truncated to end with "...she had a man".)
So there are two ways to specify such a truncation: counting words or counting letters.
Here's a couple of functions for doing those. The first fetches the first n words from the string, while the second fetches as many words as it can short of exceeding a maximum of n characters.
Some points that both functions have in common:
"Word" is defined as "any consecutive series of non-whitespace characters (that can't be made any longer without including whitespace)". The string "a sentence fragment - and another." contains six words.
Leading and trailing whitespace is trimmed from the returned string.
Apart from what is trimmed, all whitespace in the returned string is preserved unchanged from the original.
The functions really only reassemble the words; a third function does all the splitting-into-words part for both.
The functions are poorly named.
function split_words($string)
{
return preg_split('/(?<=\S)(?=\s)/', ltrim($string));
}
function truncate_words($string, $wordcount)
{
return join('', array_slice(split_words($string), 0, $wordcount));
}
function truncate_words_to_length($string, $stringlength)
{
$bits = split_words($string);
$len = $i = 0;
while($len<=$stringlength)
{
$len += strlen($bits[$i++]);
}
return rtrim(join('', array_slice($bits, 0, $i-1)));
}
Just because I like one-liners:
function truncate_words_to_length($string, $length)
{
return(array_shift(explode(chr(0), wordwrap($string, $length, chr(0)))));
}
- Edited
It's a common idiom (at least, it seems to be the standard approach in code I've seen):
$array = array();
loop
{
// Build $chunk array
$array = array_merge($array, $chunk);
}
But it's significantly faster to exploit the fact that [man]array_merge/man can take arbitrarily many arguments:
$array = array(array());
loop
{
// Build $chunk array
$array[] = $chunk;
}
$array = call_user_func_array('array_merge', $array);
When I say "significantly" here I'm talking orders of magnitude. Even if you're only merging two two-element arrays the latter method is already twice as fast, and the savings only improve as the number of arrays and the number of elements increase. I kid you not when I say that, On My Machineâ„¢, merging 20,000 arrays was more than two thousand times slower when it was done consecutively than as a single twenty-thousand-argument call to array_merge().
[Edit: later... ] What's more: PHP 7's destructuring operator ...
obviates the use of call_user_func_array
: instead, the last line becomes
$array = array_merge(...$array);
[Edit: later still... ] From PHP 8, array_merge
does the Right Thing when it's given no arguments (it returns an empty array). So the extra-nested array(array())
isn't needed any more, and can just be array()
. Or, more concise yet, []
.
Nothing earth-shaking here. Just something I threw together today because I've been using [man]parse_ini_file[/man] a lot lately, wanted to play with some PHP5 coding stuff, and to use phpdocumentor (the API documentation is online here on my site).
<?php
/**
* @author Charles Reace (www.charles-reace.com)
* @version 1.0
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/
/**
* Read data from ini file and create a data array or object
*
* @link http://www.php.net/parse_ini_file See the parse_ini_file() documentation for info on the ini file syntax
*/
class IniFile
{
/**
* @var string relative or absolute path to the ini file
*/
private $iniFilePath = '';
/**
* Constructor
*
* @param string $path (optional) path to ini file
*/
public function __construct($path = '')
{
if($path != '')
{
$this->setIniFilePath($path);
}
}
/**
* Set the ini file path
*
* Throws exception on invalid path
*
* @param string $path path to the ini file
* @return boolean
*/
public function setIniFilePath($path)
{
if(is_readable($path))
{
$this->iniFilePath = $path;
return(TRUE);
}
else
{
throw new Exception("$path does not exist or is not readable.");
}
}
/**
* parse ini file and return data as an object
*
* throws exception if path not set
*
* @return object
*/
public function getIniDataObj()
{
if(empty($this->iniFilePath))
{
throw new Exception("No ini file path defined");
}
$dataArray = parse_ini_file($this->iniFilePath, TRUE);
$dataObj = new stdClass();
foreach($dataArray as $key => $value)
{
if(is_array($value))
{
foreach($value as $key2 => $value2)
{
$dataObj->$key->$key2 = $value2;
}
}
else
{
$dataObj->$key = $value;
}
}
return($dataObj);
}
/**
* parse ini file and return data as an array
*
* throws exception if path not set
*
* @return array
*/
public function getIniDataArray()
{
if(empty($this->iniFilePath))
{
throw new Exception("No ini file path defined");
}
return(parse_ini_file($this->iniFilePath, TRUE));
}
}
// sample usage:
try
{
$ini = new IniFile('path/to/config.ini');
$configDataObject = $ini->getIniDataObj();
printf("<pre>%s</pre>", print_r($configDataObject, 1));
}
catch(Exception $e)
{
user_error("<pre>$e</pre>");
}
I know this isn't Code Critique, but a few things occur to me NogDog:
Since you are returning the parsed ini file as a stdClass object, why not just allow IniFile object to be queried for parameters. So you'd just have to write $ini->$key, rather than $ini->getIniDataObj()->$key. A bit of __get magic would do the trick. You could use ArrayAccess and Iterator from the SPL to provide the array behaviour too.
In fact encapsulating array data is such a common task I have a class which does just that. I'll paste it here if you want a look.
Also how about IniReaderException rather than plain old Exception?
this is a little function i made for my site. basically it gets all the tutorial data that has been posted, checks to see if any tutorials with the same name already exist, and if so, what version it is. If there are already 5 versions then it wont allow any more. Its nothing special but it does the job
submit_tutorial.php file:
session_start();
/*********************************************************
**********************************************************
** /support/includes/scripts/submit_tutorial.php **
** ©2007 Insight Computer Support **
** Version: 1.0 **
** Author: Tyrone Westall **
** Description: This script gathers the tutorial data**
** that is posted by the user, then sends it to the **
** main function **
**********************************************************
*********************************************************/
include('global_vars.php');
include('global_funcs.php');
include('global_funcs_index.php');
connect_db();
check_page_enabled("Tutorial Submission");
// Get incoming data
$user_posted = $_POST['user'];
$section_posted = $_POST['section'];
$title_posted = $_POST['title'];
$body_posted = $_POST['body'];
// Put values into a session just in case the user needs to be returned back to the tutorials page because of an error. This way they dont lose all the stuff they typed in
$_SESSION['user_temp'] = $user_posted;
$_SESSION['section_temp'] = $section_posted;
$_SESSION['title_temp'] = $title_posted;
$_SESSION['body_temp'] = $body_posted;
// Check to see if any of the boxes are empty
if(is_null($user_posted))
{
header("Location: http://www.insightpcs.net/support/?page=tutorials§ion=submit&errid=1&sid=$sid");
}
else if(is_null($section_posted))
{
header("Location: http://www.insightpcs.net/support/?page=tutorials§ion=submit&errid=2&sid=$sid");
}
else if(is_null($title_posted))
{
header("Location: http://www.insightpcs.net/support/?page=tutorials§ion=submit&errid=3&sid=$sid");
}
else if(is_null($body_posted))
{
header("Location: http://www.insightpcs.net/support/?page=tutorials§ion=submit&errid=4&sid=$sid");
}
else
{
// Add the tutorial to the database
tutorial_add($user_posted, $section_posted, $title_posted, $body_posted);
}
function called by above file
function tutorial_add($user_posted, $section_posted, $title_posted, $body_posted)
{
$user_posted = addslashes($user_posted);
$section_posted = addslashes($section_posted);
$title_posted = addslashes($title_posted);
$body_posted = addslashes($body_posted);
$user_posted = htmlentities($user_posted, ENT_QUOTES);
$section_posted = htmlentities($section_posted, ENT_QUOTES);
$title_posted = htmlentities($title_posted, ENT_QUOTES);
$body_posted = htmlentities($body_posted, ENT_QUOTES);
$date_time = $datetime;
// Check to see if there is a tutorial with the same title. If so, add (2) onto the end of it. If it has 2 on the end already, add 3, and so on and so forth until 5 is reached, then no more tutorials with that title can be used.
$statement = "SELECT * FROM site_tutorials WHERE tutorial_name='$title_posted'";
$query = mysql_query($statement) or die(mysql_error());
if(mysql_fetch_row($query))
{
$result = mysql_result($query, 0, "tutorial_version");
switch ($ver){
case ($result == "(Version 1)"):
$ver = "(Version 2)";
$array = array($title_posted, $ver);
$title = implode(" ", $array);
case ($result == "(Version 2)"):
$ver = "(Version 3)";
$array = array($title_posted, $ver);
$title = implode(" ", $array);
break;
case ($result == "(Version 3)"):
$ver = "(Version 4)";
$array = array($title_posted, $ver);
$title = implode(" ", $array);
break;
case ($result == "(Version 4)"):
$ver = "(Version 5)";
$array = array($title_posted, $ver);
$title = implode(" ", $array);
break;
case ($result == "(Version 5)"):
header("Location: http://www.insightpcs.net/support/?page=tutorials§ion=submit&errid=5&sid=$sid");
break;
}
// Create a separate statement to accomodate for the above possibility
$statement = "INSERT INTO site_tutorials VALUES ('', '$section_posted', '$title_posted', '$user_posted', '$body_posted', '$date_time', '$ver')";
mysql_query($statement) or die(mysql_error());
header("Refresh: 3; url=http://www.insightpcs.net/support/?page=tutorials§ion=submit&errid=none&sid=$sid");
}
else
{
// Tutorial doesnt exist, so make new tutorial
$statement = "INSERT INTO site_tutorials VALUES ('', '$section_posted', '$title_posted', '$user_posted', '$body_posted', '$date_time', '(Version 1)')";
mysql_query($statement) or die(mysql_error());
header("Refresh: 3; url=http://www.insightpcs.net/support/?page=tutorials§ion=submit&errid=none&sid=$sid");
}
unset($_SESSION['body_temp'], $_SESSION['title_temp']);
}
- Edited
The array-traversal functions like array_map() and so on that take a callback function and an array and does something to the one with the other are nice, but they have a drawback in that there is no straightforward way to supply additional arguments to the callback. A lot of the time the only sensible approach is go back to using a loop.
array_map(), for example, lets you supply as many arguments as you like to the callback, but you have to pass arrays to array_map. Let's say you have an array of strings and you want to remove the first and last three characters from each of them. substr() is the obvious function of choice, but that only works on one string and we have a whole array. So what do we do?
The most obvious idea is to forgo the idea of using a traversal function:
foreach($array as &$string)
{
$string = substr($string, 3, -3);
}
Or maybe temporary arrays containing the other arguments duplicated the appropriate number of times (yuck):
$array = array_map('substr', $array,
array_fill(0,count($array), 3),
array_fill(0, count($array), -3));
Or a specialised callback function:
function substr_chop_ends($string)
{
return substr($string, 3, -3);
}
$array = array_map('substr_chop_ends', $array);
Perhaps using create_function:
$array = array_map(create_function('$string','return substr($string, 3, -3);'), $array);
But we're losing flexibility one way or the other with those last two: our callback function really can only trim the end characters off. If we want it to do something else we have to write another function.
Well, there is another approach (prepare to vom):
$GLOBALS['substrCallback_start'] = 3;
$GLOBALS['substrCallback_length'] = -3;
function substrCallback($string)
{
return strtr($string, $GLOBALS['substrCallback_start'], $GLOBALS['substrCallback_length']);
}
$array = array_map('substrCallback', $array);
But we won't go there.
The previous two approaches seem like the most practical, but what is needed is
a way to generate them as needed instead of having to hand craft every one.
$array = array_map(create_function('$string','return substr($string, '.$start.', '.$length.');'), $array);
But of course we might want to do this with other functions, with different sets of arguments; we might not even want our array to be the first argument. There still seems to be more hand-carving going on than necessary.
Enough with the introduction. Here's the code being shared.
$bindings = array();
function bind($f, $bound_args)
{
$GLOBALS["bindings"][$ref = count($GLOBALS["bindings"])] = array($f, $bound_args);
return create_function('', '$args = func_get_args();
list($f, $bound_args) = $GLOBALS["bindings"]['.$ref.'];
$args = array_combine(array_slice(array_diff_key(range(0,' . (max(array_keys($bound_args))-1) . '+count($args)),$bound_args),0,count($args)),$args) + $bound_args;
ksort($args);
return call_user_func_array($f, $args);'
);
}
The bind() function takes two arguments, the name of a callback function, and an array of values that are to be bound to the function's parameters, effectively pre-filling them in advance. In the ongoing example:
$chop_ends = bind('substr', array(1=>3, 2=>-3));
$array = array_map($chop_ends, $array);
The format of the $bound_args argument is not really that complicated. As you can see, it's an array. The indices of the array match up with the callback function's parameters (counting from 0), and the values as you might expect are the corresponding arguments. So "substr($string, $start, $length)", when bound with array(1=>3, 2=>-3), becomes "substr($string, 3, -3)".
The code works by basically doing just that. It uses a global array $GLOBALS['bindings'] (ugly, I know, but its contents need to be globally accessible even inside functions - an object-oriented version might be smarter but, apart from namespace niceness, not significantly different: the functions contained therein are still by definition global).
Because the values of $bound_args aren't necessarily serialisable (let alone representable as source code), they can't be embedded directly into the callback under construction (the number one most irritating thing about PHP's handling of runtime-generated functions). What's more, the function names generated by create_function aren't directly embeddable into the code of new created functions without a lot of arsing about with chr(0) and whatnot (no, wait, that's the most irritating thing).
So $GLOBALS['bindings'] stores both of these items (the name of the callback having its arguments bound and the bound arguments themselves) in such a way that they'll be accessible to the callback being constructed.
Then the callback is constructed. All that hairy long line setting $args does is merges $bound_args with any other arguments that might be passed to the callback when it is actually called in such a way that the bound arguments go where they should go and the passed arguments fill in the remaining spaces.
For example, consider the line
$blag = bind('wibble', array(4=>'foo', 2=>'bar'));
With that assignment, $blag becomes callable. Let's do that.
$blag('a','b','c','d','e');
What does that result in? Well, the function $blag takes the name of the callback ('wibble') and its bound arguments (array(4=>'foo', 2=>'bar')), merges with latter with the other arguments that have been passed (array('a','b','c','d','e')) and applies the callback to them. Result:
wibble('a','b','bar','c','foo','d','e');
The thing to take away from that example is how the bound arguments and the passed arguments were merged. Note that "bar" is the second argument and "foo" the fourth (and "a" the zeroth).
Like I said, the whole point of the global $bindings array is that it is not necessary to use only callbacks and arguments that can be written down in PHP source code. The callback could be the name of a created function, or it could be an array that represents a class or object method; and the bound arguments could be pretty much anything that could be passed to a function (including objects and resources).
The global $bindings array is also its strongest weakness: anything that goes in that array stays in that array: it will sit around in there until the end of the script. So of course will the callback that each entry in the array is associated with.
Oh, one last thing. note that bind() itself only stores the bound arguments: they're used only when the created function is called. This matters for volatile things like objects and resources that may have had their state changed in the meantime.
And dribble off into some weedy examples...
function t1($a, $b, $c)
{
return $a+$b+$c;
}
$t3 = bind(bind('t1', array(3)), array(2));
echo $t3(42),"\t",$t3(1),PHP_EOL;
class Foo
{
static function bar($a,$b,$c)
{
return $a+$b+$c;
}
}
$t3 = bind(array('Foo','bar'), array(3));
$t3 = bind($t3, array(2));
echo $t3(42),PHP_EOL;
$t3 = bind(array('Foo','bar'), array(3,2));
echo $t3(42),PHP_EOL;
class Wibble
{
private $spink = 3;
function Womble($a,$b,$c)
{
return $a+$b+$c;
}
function screech()
{
$t3 = bind(array($this,'Womble'), array($this->spink, 2));
echo $t3(42);
}
}
$wibble = new Wibble;
$wibble->screech();
echo PHP_EOL;
Oh, and this entire post is obsolete as of PHP 5.3. So if you're using that version, reading this was a waste of your time You'd write:
function chop_ends($start, $end)
{
return function($word)use($start, $end)
{
return substr($word, $start, $end);
};
}
$array = array_map(chop_ends(3, -3), $array);
[Edit much much later:] PHP 7.4 introduced arrow functions. Those automatically close over variables from the outer context that are explicitly used in the function body.
$start = 3;
$end = -3;
$array = array_map(fn($word) => substr($word, $start, $end), $array);
- Edited
Bump
So you've got a grab-bag of items as an associative array; user preferences, say, or widget settings. You pass it around to various methods which may or may not be interested in some of the contents (you don't necessarily know what user preferences a given method might be interested in). The method rummages around in the bag to see if there are any items in there that it's interested in by looking at the keys for any that are applicable. The rest it ignores.
A common step at this point is to make a new array that contains only the relevant elements of the grab-bag. The following is a function to do exactly that; without requiring a foreach loop or a test or any other such traversal of the grab-bag's contents.
function select_by_keys($grabbag, $keys)
{
return array_intersect_key($grabbag, array_flip($keys));
}
To use:
// $options = array('size'=>'100px', 'fontNum'=>42, 'fontName'=>'bedrock', 'foo'=>'bar');
static $font_elements = array('fontName', 'fontNum', 'font');
$this->fonts[] = select_by_keys($options, $font_elements);
The new element of $this->fonts
will be ['fontName'=>'bedrock', 'fontNum'=>42]
. Because there was no $options['font']
, there's no 'font'
element in the new array either.
If you want defaults to be used instead, you'd basically have to supply them as well as the keys: that might as well be in the form of an associative array. And that's just a use of the [font=monospace]+[/font] operator.
static $defaults = array('fontName'=>'Helvetica')
$this->fonts[] = select_by_keys($options, $font_elements) + $defaults;
- Edited
"Currying" is the process of getting an n-parameter function and turning it into a function that takes some of the parameters and returns a function that takes the rest of the parameters and returns the result of the original function when passed all of the parameters. Like if I had a function pow($n, $m) I could turn that into curried_pow($n) that returns a function anonymous($m). For a given $n and $m the result of calling those two one-argument functions would be equivalent to calling the original two-argument function.
This may not be useful, but it is certainly intriguing, and intimates all sorts of possibilities.
function curry($f)
{
if(!is_callable($f))
{
trigger_error(E_USER_WARNING, "curry()'s argument needs to be callable.");
return null;
}
return function($x)use($f)
{
// An arbitrary number of arguments could be passed...
return function()use($x,$f)
{
$args = func_get_args();
// ...and the first one already HAS been...
array_unshift($args, $x);
return call_user_func_array($f, $args);
};
};
}
In principle, any function can be curried. In PHP practice, functions can have a variable number of arguments: currying str_replace() would be difficult, because once you've supplied three arguments should you be left with a string or a function that takes a fourth?
In the example below I show that curry() will choose the former option.
$fixed_search = curry('str_replace');
$foo_search = $fixed_search('foo');
echo $foo_search('bar', "This food is so bad that only a fool would want to eat it.");
I could have curried $foo_search, and finished up with $fixed_search = curry('str_replace'); $foo_search = $fixed_search('foo'); $bar_replace = $foo_search('bar'); echo $bar_replace("....");
. But that would have made me wish that PHP's syntax supported curry('str_replace')('foo')('bar')("....");
.... (Which, starting with v5.4, it does).
C doesn't have strings - it has arrays of type char: "foo" is actually shorthand for {'f', 'o', 'o', '\0x00'}
C doesn't have arrays - it has pointers into allocated regions of memory: foo[4] is actually shorthand for *(foo+4)
PHP doesn't have pointers into allocated regions of memory - it has arrays and strings.
They don't work the same way.
But if you're porting a C implementation of some algorithm, as an intermediate step on the way to translating it into PHP, it would be nice to emulate the mechanics of the former using those of the latter.
Sometimes C's strings and arrays can be replaced by PHP's strings and arrays without too much grief; but as soon as the C program starts using pointer manipulations - which is usually quite quickly - it's not so simple, especially once they start being passed from function to function. Sometimes passing them by reference is enough, but all too often it's not.
We can model C's "allocated region of memory" as a PHP array; and we can model a pointer into that region as an array index. When we pass a "pointer", we need to pass both the array index, and the array the index indexes. And we have to pass the array as a reference, because any element within it is supposed to represent a specific location in memory, and all pointers to that location should see the same value, even if it changes.
So here's the trick. A C pointer is emulated in PHP by an array of two elements; the zeroth element is a reference to an array, and the first is an integer that indexes that array.
Allocating memory becomes creating an array of suitable size, and then putting it into an array of the above type; the index is initialised to zero.
char *foo = malloc(count * sizeof(char*));
becomes
$foo = array_fill(0, $count, 0); // Allocate the space
$foo = array(&$foo, 0); // This is where the magic happens
(Attentive readers will note that this is more like calloc, since it initialises the contents of the array to 0 - when we ask PHP for an array, it has to use something for the elements if they're to exist).
Getting an element of the array:
a = foo[4]; b = *foo;
becomes
$a = $foo[0][$foo[1] + 4];
$b = $foo[0][$foo[1]];
And that's probably the big drawback to this scheme.
Advancing the array pointer:
foo += 3;
is just
$foo[1] += 3;
Copying a pointer
c = foo;
is just
$c = $foo;
Copying an offset pointer is probably best done by copying the pointer and then offsetting the copy.
d = foo + 3;
=>
$d = $foo; $d[1] += 3;
. The alternative is somewhat messier and basically involves taking the emulated pointer apart and putting it back together again:
$d = array(&$foo[0], $foo[1] + 3);
This is necessary so that the new pointer continues to refer to the same allocated memory - that the first element is a reference, the second is a value.
For strings, a couple of utility functions are generally called for, especially with regard to PHP's interpreting the integer 0 as the one-character string '0' instead of an ASCII NUL.
function toc($php_string)
{
$c_string = array_map('ord', str_split($php_string));
$c_string[] = 0;
return array(&$c_string, 0);
}
function fromc($c_string)
{
list($mem, $off) = $c_string;
array_splice($mem, 0, $off);
$nul = array_search(0, $mem);
if($nul !== false)
{
array_splice($mem, $nul);
}
else
{
// Naughty, naughty - you passed a non-terminated string!
// You should be glad this isn't REALLY C or I'd slap you.
}
return join('', array_map('chr', $mem));
}
$input = "This is a test";
var_dump($input);
$c = toc($input);
var_dump($c);
$c[1] += 8;
$c[0][$c[1]] -= 32;
var_dump($c);
$output = fromc($c);
var_dump($output);
Here's the sort of thing that can result from the conversion:
/* Copy SRC to DEST, returning the address of the terminating '\0' in DEST. */
char *
__stpcpy (char *dest, const char *src)
{
register char *d = dest;
register const char *s = src;
do
*d++ = *s;
while (*s++ != '\0');
return d - 1;
}
function __stpcpy($dest, $src)
{
$d = $dest;
$s = $src;
do
{
$d[0][$d[1]++] = $s[0][$s[1]];
} while($s[0][$s[1]++] != 0);
$d[1]--;
return $d;
}
Weedpacket wrote:C doesn't have strings - it has arrays of type char: "foo" is actually shorthand for {'f', 'o', 'o', '\0x00'}
False. C does not have a string type in the core language or standard library, but C does indeed have strings, where "a string is a contiguous sequence of characters terminated by and including the first null character".
Weedpacket wrote:C doesn't have arrays - it has pointers into allocated regions of memory: foo[4] is actually shorthand for *(foo+4)
False. C does have arrays. An array is not a pointer, although in most contexts an array is converted to a pointer to its first element.
That said, these would not be important if you are translating C code to PHP since the former is a matter of definition, and if the C code suffers from the pitfalls of failing to understand that arrays are not pointers, then the C program would likely be incorrect to begin with.
laserlight wrote:C does indeed have strings, where "a string is a contiguous sequence of characters terminated by and including the first null character".
Well, that's pretty much what I said (and you quoted); strings in C are syntactic sugar. The only difference is whether you choose to describe it in English as "a contiguous sequence of characters terminated by and including the first null character" or illustrate it by example in C as [font=monospace]{'f', 'o', 'o', \0x00}[/font].
laserlight wrote:C does have arrays. An array is not a pointer, although in most contexts an array is converted to a pointer to its first element.
True, but the exceptions don't apply for the emulation, since they mainly result from considerations of the byte size of the array's base type - something which in PHP is neither relevant to the size of the array nor well-defined anyway (since PHP's arrays don't have a "base type"). So that makes C's sizeof operator useless here (something I hinted at in my mention of [font=monospace]malloc[/font]) - even more useless is the C idiom [font=monospace]sizeof(arr)/sizeof(arr[0])[/font] for finding out how many elements an array has. (Incidentally, that's not the only housekeeping issue that drops out during the conversion; although you can implement [font=monospace]free($foo)[/font] as [font=monospace]$foo[0]=null;[/font], there isn't really much point in the long run, and you might just as well set [font=monospace]$foo=null[/font] directly (yep, a null pointer is best implemented as a null) and let the runtime scrub up iff it was the last live pointer into its chunk of memory.)
The downside is that you can't declare an array of long and then turn around and treat it as an array of int; in PHP you have to code that conversion explicitly. (Of course, if you do that in C anyway you're either (a) looking for a smack, or (b) also using it as an environment test.)
Similarly, the & operator is also useless because in PHP we again don't actually have that information; and as for string literals, that case is one I addressed explicitly. Otherwise an array is as I described, and a variable of type "array of int" is indeed a "pointer to int". Equivalent to a pointer to int, if you insist, but the whole point of emulation is to construct something (functionally) equivalent.
I signed up just to say "Thank you!" to Weedpacket for posting this slick bit of code. It was exactly what I needed. I'm linking some keywords in a block of text, but I only want to link them if they aren't already in a link. This method works perfectly.
Thanks for taking the time to code this function- it's super handy.
Just a quick note...
PHP's custom sort functions take a comparator function as callback (of two given elements, decide which is larger). Sometimes though it's easier to give each array element a score and then order by that (it's often somewhat faster too, since sorting with a comparator function means that each element basically gets examined twice - this way it only needs to be scored once).
Consider it a refinement of my earlier "Mutant Schwartzian transforms".
Key association is retained.
function sort_by(&$array, $scorefn)
{
$vals = array_values($array);
$array = array_map($scorefn, $vals);
asort($array);
foreach($array as $key=>$val)
{
$array[$key] = $vals[$key];
}
}
- Edited
I needed to have several independent (pseudo-)random number generators, where "independent" meant that the numbers yielded by one generator weren't affected by calls to any of the other generators (as would happen if they all ran off PHP's internal RNG).
I didn't need Mersenne Twister levels of randomness for my purpose, so just picked the first algorithm to hand; specifically, the RNG that appears in the GNU Scientific Library under the name of "zuf".
The generator (actually, the generator factory) is implemented as a single function. As usual, examples of its use follow.
function zuf($s = 0)
{
$state_n = 0;
$state_u = array();
$kl = 9373;
$ij = 1802;
if($s == 0)
{
$s = 1802;
}
$ij = $s;
$i = intval($ij / 177) % 177 + 2;
$j = $ij % 177 + 2;
$k = intval($kl / 169) % 178 + 1;
$l = $kl % 169;
for($ii = 0; $ii < 607; ++$ii)
{
$x = 0;
$y = 0.5;
for($jj = 1; $jj <= 24; ++$jj)
{
$m = $i * $j % 179 * $k % 179;
$i = $j;
$j = $k;
$k = $m;
$l = ($l * 53 + 1) % 169;
if($l * $m % 64 >= 32)
{
$x += $y;
}
$y *= 0.5;
}
$state_u[$ii] = $x * (1 << 24);
}
return function()use(&$state_n, &$state_u)
{
$n = $state_n;
$m = ($n + (607 - 273)) % 607;
$t = $state_u[$n] + $state_n[$m];
while($t > 1 << 24)
{
$t -= 1 << 24;
}
$state_u[$n] = $t;
if($n == 606)
{
$state_n = 0;
}
else
{
$state_n = $n + 1;
}
return $t;
};
}
// Four different generators, using two different seeds
// (to illustrate independence).
$rng1 = zuf(0);
$rng2 = zuf(42);
$rng3 = zuf(0);
$rng4 = zuf(42);
for($i = 0; $i < 10; ++$i)
{
echo $rng1(), ' ';
}
echo "\n";
for($i = 0; $i < 10; ++$i)
{
echo $rng3(), ' ';
}
echo "\n";
for($i = 0; $i < 10; ++$i)
{
echo $rng2(), ' ';
}
echo "\n";
for($i = 0; $i < 10; ++$i)
{
echo $rng1(), ' ';
}
echo "\n";
for($i = 0; $i < 10; ++$i)
{
echo $rng3(), ' ';
}
echo "\n";
for($i = 0; $i < 10; ++$i)
{
echo $rng2(), ' ';
}
echo "\n";
for($i = 0; $i < 20; ++$i)
{
echo $rng4(), ' ';
}
echo "\n";
[Edit years later: PHP 8.2 has a revamped PRNG system available that allows multiple independent instances of multiple RNGs. So, basically, it does what the code here was for.]
- Edited
Like most real-world libraries, PHP uses Quicksort for its sorting algorithm. Done right, this is a nice and efficient algorithm for general-purpose use (in specific circumstances other algorithms are better choices).
If Quicksort has one big drawback, it's the fact that it's not stable. If two elements in the input array are considered "equal" for the purposes of comparison, the order in which they appear in the output is undefined. What makes this a drawback is that it interferes with constructing complex sort criteria (you can't break the task down into "sort by criterion A" followed by "sort by criterion B" because the latter sort messes up the results of the former): you have to sort once and check all the criteria during that sort, instead of being able to "chain" sort criteria together.
When I was faced with this problem I started out implementing Mergesort (which is stable and its worst-case performance is comparable to Quicksort's average-case performance), but then I realised I could stabilise Quicksort instead. I wrote this as a drop-in replacement for usort; comparable "ssort", "srsort", "sasort", etc. can be written using the same model.
function susort(&$array, $cmp)
{
$array = array_merge(null, range(1, count($array)), $array);
usort($array, function($a, $b)use($cmp)
{
return $cmp($a[1], $b[1]) ?: ($a[0] - $b[0]);
});
$array = array_column($array, 1);
}
[Edit quite a while later: PHP's own sorting is stable as of v8.0, so you don't need to do it yourself any more.]
Just a quick thing: Leap years in the Gregorian calendar without any branch instructions.
function is_leap_year($year)
{
return !($year % (400 - 396 * ($year % 100 != 0)));
}
Weedpacket;11047287 wrote:Just a quick thing: Leap years in the Gregorian calendar without any branch instructions.
function is_leap_year($year) { return !($year % (400 - 396 * ($year % 100 != 0))); }
Couldn't you just use PHP's date() function for that?
return date('L', $year);//Returns 1 for leap year or 0 if not