|
<?php |
|
/** |
|
* TSequence is an array trait with numerically sortable keys. Keys can be |
|
* negative or positive integers or floats. It does not matter whether they are |
|
* actually numeric types or string and if they are strings they are allowed to |
|
* contain any character so long as they start with something numeric. |
|
* The non-numeric ending of the string key, if present, is called the 'tag'. |
|
* |
|
* You can optionally protect to an already assigned element by automatically |
|
* incrementing the numeric portion of the key you provided until a free slot is |
|
* found. (see offsetSet method) |
|
* |
|
* Note that only |
|
* |
|
* Whenever you fetch the array as a whole with the getArrayCopy or getArrayRef |
|
* methods, the array is sorted by (the numeric prefix portion of the) key. |
|
* |
|
* @property array $seq object's array data. When setting, same as calling seq(). |
|
* aliases: $data, $array |
|
* @property bool $auto_push determines whether to use $key for [] 'null keys' |
|
* aliases: $push, $autoPush |
|
* @property mixed $incr the value added to [$key] to avoid overwriting or not if falsy |
|
* aliases: $auto_incr, $autoIncr |
|
* @property mixed $key manually set key used with auto_push for temp use |
|
* or, if null/false, sets $auto_push boolean |
|
* aliases: $push_key, $pushKey |
|
* @property mixed $depth default depth of search with search() |
|
* aliases: $search_depth, $searchDepth |
|
* @property mixed $search_mode default mode of search with search() |
|
**/ |
|
trait TSequence #implements Serializable, IteratorAggregate, ArrayAccess, Countable |
|
{ |
|
protected $seq = array(); |
|
protected $incr = false; |
|
protected $auto_null = false; |
|
protected $key = 0; |
|
protected $depth = 1; |
|
protected $search_mode = 'keys'; |
|
|
|
static protected $hasnum = '/^[-+]?[0-9].*$/'; |
|
static protected $ksplit = '/^([-+]?[\d]+\.?[\d]*)(.*)$/'; |
|
static protected $kparse = '/^([-+]?[\d]+\.?[\d]*)(\s*)(.*)$/'; |
|
|
|
function __get($prop) { return $this->_validGetSet($prop, 'get'); } |
|
|
|
protected function _validGetSet($name, $mode=true) { |
|
$lu = array( |
|
'auto_null'=>'auto_null', 'push'=>'auto_null', 'auto'=>'auto_null', # |
|
'incr'=>'incr', 'auto_incr'=>'incr',# |
|
'push_key'=>'key', 'key'=>'key', # |
|
'seq'=>'seq', 'data'=>'seq', 'array'=>'seq',# |
|
'depth'=>'depth', 'search_depth'=>'depth', |
|
'search_mode'=>'search_mode', |
|
); |
|
if (isset($lu[$name])) return $mode==='get' ? $this->{$lu[$name]} |
|
: array('is'=>'prop', 'name'=>$lu[$name]); |
|
|
|
if ( ($snake=strtolower(preg_replace('/(?<!^)[A-Z]/','_$0',$name))) |
|
&& isset($lu[$snake]) ) return $mode==='get' ? $this->{$lu[$snake]} |
|
: array('is'=>'prop', 'name'=>$lu[$snake]); |
|
|
|
if (preg_match('/^(?:push_)?(?:key_)?(.+)$/',$snake,$n)&&isset($n[1]) |
|
&& in_array($n[1],array('num','idx','tag','space'),true) |
|
) { |
|
preg_match(self::$kparse,$this->key,$c); $c = array_pad($c,4,''); |
|
$arr = array( 'is'=>'meta', 'name'=>$n[1], 'key'=>$this->key, |
|
'num'=>$c[1],'idx'=>(int)$c[1],'space'=>$c[2],'tag'=>$c[3] |
|
); |
|
return $mode==='get' ? $arr[$n[1]] : $arr; |
|
} |
|
if (property_exists($this, $name)) return $mode==='get' ? $this->$name |
|
: array('is'=>'other', 'name'=>$name); |
|
if (property_exists($this, $snake)) return $mode==='get' ? $this->$snake |
|
: array('is'=>'other', 'name'=>$snake); |
|
|
|
if ($mode==='get') $mode = E_USER_ERROR; |
|
$msg = get_called_class()."->$name invalid property or meta-property name"; |
|
if (!$mode) $ret = array('is'=>null, 'name'=>$msg); |
|
trigger_error($msg, (int)$mode); return array(); |
|
} |
|
function __set($prop, $val) { |
|
extract($this->_validGetSet($prop, E_USER_ERROR)); |
|
if ($is==='prop') { |
|
if ($name==='seq') return $this->seq($val);# fast shortcut for this |
|
if ($name==='incr') { |
|
if (!$val) $this->incr = false; |
|
else $this->incr = is_numeric($val) ? $val : 0.01; |
|
return $this->incr; |
|
} |
|
if ($name==='auto_null') return $this->auto_null = (bool)$val; |
|
if ($name==='key') { |
|
if ($val===null||$val===false) return $this->auto_null=(bool)$val; |
|
$k = is_numeric($val) ? (string)($val+0) : "$val"; |
|
if (preg_match(self::$hasnum,$k)) return $this->key = "$k"; |
|
} |
|
if ($name==='depth') return $this->depth = (int)$val; |
|
if ($name==='search_mode') return $this->search_mode = $val; |
|
} elseif ($is==='meta') { |
|
if ($name==='tag') { |
|
# kill space if tag is being deleted or if tag provided has space |
|
if (!$tag || $tag[1]===' ') $space = ''; |
|
return $this->key = $num.$space.$tag; |
|
} |
|
if ($name==='idx' && is_int($val)) { |
|
$arr = explode('.', $num, 2); |
|
$frac = isset($arr[1]) ? ".{$arr[1]}" : ''; |
|
return $this->key = ((string)($val+0)).$frac.$space.$tag; |
|
} |
|
if ($name==='num' && is_numeric($val)) |
|
return $this->key = ((string)($val+0)).$space.$tag; |
|
if ($name==='space' && ($has=ctype_space($val)) || !$val) |
|
return $this->key = $num . ($has ? $val : '') . $tag; |
|
} |
|
return $this->kTypeErr($val); |
|
} |
|
/** |
|
* @return the first key with current sorting (not after ksort). |
|
*/ |
|
function resetKey() { reset($this->seq); return key($this->seq); } |
|
/** |
|
* @return the last key with current sorting (not after ksort). |
|
*/ |
|
function endKey() { end($this->seq); return key($this->seq); } |
|
/** |
|
* @return the last key after hypothetical ksort |
|
*/ |
|
function maxKey() { return max(array_keys($this->seq)); } |
|
/** |
|
* @return the first key after hypothetical ksort |
|
*/ |
|
function minKey() { return min(array_keys($this->seq)); } |
|
|
|
/** |
|
* newKey() adds a new element to the sequence with assurance the key did not |
|
* previously exist, whether set to null or not. A new key MUST be passed |
|
* as the first argument and, if it exists or is invalid, false is returned. |
|
* |
|
* @param mixed $val value to set or first of many in array if there are more args |
|
* @return mixed the key of the newly created element or false |
|
*/ |
|
function newKey($key=null, $val=null) |
|
{ |
|
$k = is_numeric($key) ? (string)($key+0) : "$key";# remove .0 trail |
|
if (!preg_match(self::$hasnum,$k)) return false; |
|
|
|
if (!array_key_exists($k, $this->seq)) { |
|
$this->seq[$k] = $val; return $k; |
|
} |
|
if (func_num_args()>2) {# multi-arg value becomes array |
|
$val=func_get_args(); unset($val[0]); $val=array_values($val); |
|
} |
|
do $num += $incr; while( array_key_exists("$num$end", $this->seq) ); |
|
$this->key="$num$end"; $this->seq[$k] = $val; return $k; |
|
} |
|
|
|
/** |
|
* newIncr() adds a new element to the sequence with assurance the key did not |
|
* previously exist, whether set to null or not. The new key will be an |
|
* version of $this->key with it's numeric portion incremented. |
|
* |
|
* The first argument can be used to explicitly set a temporaray increment |
|
* value or, if null, $this->incr will be used if truthy or 0.01 as default. |
|
* |
|
* @param mixed $incr increment and/or tag information for new key |
|
* @param mixed $val value to set or first of many in array if there are more args |
|
* @return mixed the key of the newly created element |
|
*/ |
|
function newIncr($incr=null, $val=null) |
|
{ |
|
if (!is_numeric($incr)) $incr = $this->incr ? $this->incr : 0.01; |
|
|
|
if (func_num_args()>2) {# multi-arg value becomes array |
|
$val=func_get_args(); unset($val[0]); $val=array_values($val); |
|
} |
|
$k = $this->key; |
|
if (!array_key_exists($k, $this->seq)){ $this->seq[$k]=$val; return $k; } |
|
|
|
// else we need to increment, but only the numeric prefix: |
|
if ( preg_match(self::$ksplit, $k, $c) && isset($c[2])) { |
|
$num = $c[1]; $end = $c[2]; |
|
} else { $num = $k; $end = ''; } |
|
|
|
do $num += $this->incr; while( array_key_exists("$num$end", $this->seq) ); |
|
$this->key="$num$end"; $this->seq[$this->key] = $val; return $this->key; |
|
} |
|
/** |
|
* newMax() adds a new element to the sequence with assurance the key did not |
|
* previously exist, whether set to null or not. It assures the new key would |
|
* be of maximum value, meaning after a (not actually preformed) ksort. |
|
* |
|
* The optional first argument can be used to set the increment value which |
|
* otherwise would be $this->incr or 1 as a default. If the first argument is |
|
* a non numeric string, it's parsed as if it's a key, only it does not need |
|
* have a numeric prefix but if it does, that's the increment value and the |
|
* portion after is what will be the tag of the new key. |
|
* |
|
* @param mixed $incr increment and/or tag information for new key |
|
* @param mixed $val value to set or first of many in array if there are more args |
|
* @return mixed the key of the newly created element |
|
*/ |
|
function newMax($incr=null, $val=null) |
|
{ |
|
if (func_num_args()>2) {# multi-arg value becomes array |
|
$val=func_get_args(); unset($val[0]); $val=array_values($val); |
|
} |
|
if ($incr===null){ $incr=$this->incr? $this->incr : 1; $end = ''; } |
|
elseif (is_numeric($incr)) { $incr=$incr; $end = ''; } |
|
elseif (preg_match(self::$ksplit,$k,$c)){ $incr=$c[1]; $end=$c[2];} |
|
else { $incr=$this->incr? $this->incr : 1; $end = ''; } |
|
|
|
$k = $incr+(float)max(array_keys($this->seq)) . $end; |
|
if (array_key_exists($k, $this->seq)) return false; |
|
$this->key = $k; $this->seq[$k]; return $k; |
|
} |
|
/** |
|
* newMin() adds a new element to the sequence with assurance the key did not |
|
* previously exist, whether set to null or not. It is exactly the same as |
|
* append only the new key is below the lowest key using the increment |
|
* value (determined in the same way as newMax ) as a decrement. |
|
* |
|
* @param mixed $decr increment and/or tag information for new key |
|
* @param mixed $val value to set or first of many in array if there are more args |
|
* @return mixed the key of the newly created element |
|
*/ |
|
function newMin($decr=null, $val=null) |
|
{ |
|
if (func_num_args()>2) {# multi-arg value becomes array |
|
$val=func_get_args(); unset($val[0]); $val=array_values($val); |
|
} |
|
if ($decr===null){ $decr=$this->incr? $this->incr : 1; $end = ''; } |
|
elseif (is_numeric($decr)) { $decr=$decr; $end = ''; } |
|
elseif (preg_match(self::$ksplit,$k,$c)){ $decr=$c[1]; $end=$c[2];} |
|
else { $decr=$this->incr? $this->incr : 1; $end = ''; } |
|
|
|
$k = (float)max(array_keys($this->seq))-$decr . $end; |
|
if (array_key_exists($k, $this->seq)) return false; |
|
$this->key = $k; $this->seq[$k]; return $k; |
|
} |
|
/** |
|
* set() is used to 'forcefully' set an element, meaning it ignores settings |
|
* and essentially behaves as assigment with [] on a normal array would, only |
|
* when a key is not specified, it will be the $key property value, which |
|
* could have been determined from accessing or setting. |
|
* |
|
* 1 argument forcefully sets the previous key to the $arg1 value. |
|
* 2 arguments forcefully sets the the element at $arg1 to the value $arg2 |
|
* 3+ arguments forcefully sets the the element at $arg1 to the remaining |
|
* arguments, shifted to start at index 0 rather than 1. |
|
* @param mixed $arg1 can be an array key or a value being set to previous key |
|
* @param mixed $val value to set or first of many in array if there are more args |
|
* @return mixed returns $this or false if key is not valid. |
|
*/ |
|
function set($arg1, $val=null) { |
|
if (($argc=func_num_args())===1) { |
|
$this->seq[$this->key] = $arg1; return $this->key; |
|
} |
|
if ($arg1===null) $k = $this->key; |
|
else { |
|
$k = is_numeric($arg1) ? (string)($arg1+0) : "$arg1";# remove .0 trail |
|
if (!preg_match(self::$hasnum,$k)) return false; |
|
} |
|
if ($argc>2){$val=func_get_args(); unset($val[0]); $val=array_values($val);} |
|
|
|
$this->seq[$k] = $val; return $this; |
|
} |
|
/** |
|
* seq() sets the full content of the array object. It can be used |
|
* to clobber what's there or set it for the first time, as if from the |
|
* array constructor. Pass it something empty or falsy to simply clear it. |
|
* seq() is capable of accepting any number of argument and if more than one, |
|
* The entire argument array is the array being assigned (with int keys). |
|
* |
|
* @param mixed $a an array to set to or the first value of the first element |
|
* @return object returns $this |
|
*/ |
|
function seq($a) { |
|
if (($argc=func_num_args())>1) { |
|
$this->seq=func_get_args(); $this->key=$argc-1; return $this; |
|
} |
|
if (!$a) {$this->seq=array(); $this->key=0; return $this;}#clear all |
|
$m = 'getArrayCopy'; // to de-clutter complex condition below |
|
if ( is_array($a) || (method_exists($a,$m) && $a=$a->$m()||true) ) { |
|
$bak = $this->seq; $this->seq=array(); // array need to be sanitized: |
|
foreach ($a as $k=>$v) { |
|
$k = is_numeric($k) ? (string)($k+0):"$k"; # remove .0 trail |
|
if (!preg_match(self::$hasnum,$k)) return $this->kTypeErr($k,$bak); |
|
$this->seq[$k] = $v; |
|
} |
|
$this->key = $k; |
|
} |
|
else { $this->seq = array($a); $this->key=0; }# set array to one el. |
|
return $this; |
|
} |
|
/** |
|
* offsetSet() uses TSequence's Numeric Key Formatting: |
|
* |
|
* If keys is numeric, trailing zeros are stripped and anything equaling an |
|
* even number becomes an integer. Floats are converted to strings to |
|
* prevents php's behavior of converting them to ints. Any key that does not |
|
* have a numeric prefix or is not numeric will result in error. |
|
* |
|
* When the key being assigned to already exists, a number of things could |
|
* occur. If the auto_incr aka incr property is off (falsy) the value is reset. |
|
* If it is a non-zero number or true (this sets it to the default 0.01) |
|
* the number / numeric prefix, will be incremented until a free slot is found. |
|
* |
|
* When assigned with [] or [null], two things could happen. If the auto_push |
|
* property is true, The key pointed to by the $key property will be tried. |
|
* This property is the previously accessed / set key or whatever you explicitly |
|
* set it to with the ->key property. This key might or might not exist and |
|
* if it does, the rules of auto_incr apply. |
|
* |
|
* @param mixed $k key of desired element to assign |
|
* @param mixed $val value to assign |
|
*/ |
|
function offsetSet($k, $val) { |
|
if ($k===null) { |
|
if ($this->auto_null) $k = $this->key; |
|
else { $this->seq[] = $val; |
|
end($this->seq); $this->key = key($this->seq); return $this; |
|
} |
|
} else { |
|
$k = is_numeric($k) ? (string)($k+0) : "$k";# remove .0 trail |
|
if (!preg_match(self::$hasnum,$k)) return $this->kTypeErr($k); |
|
} |
|
|
|
if (!$this->incr||!isset($this->seq[$k])){ |
|
$this->key=$k; $this->seq[$this->key]=$val; return $this; |
|
} |
|
// else we need to increment, but only the numeric prefix: |
|
if ( preg_match(self::$ksplit, $k, $c) && isset($c[2])) { |
|
$num = $c[1]; $end = $c[2]; |
|
} else { $num = $k; $end = ''; } |
|
|
|
do $num += $this->incr; while( isset($this->seq["$num$end"]) ); |
|
$this->key="$num$end"; $this->seq[$this->key] = $val; return $this; |
|
} |
|
/** |
|
* offsetGet also uses TSequence's Numeric Key Formatting |
|
* If the element is not set, no error occurs and null is returned. |
|
* @param mixed $k key of desired element to fetch |
|
*/ |
|
function offsetGet($k) { |
|
$k = is_numeric($k) ? (string)($k+0) : "$k"; // sanitize |
|
if (!isset($this->seq[$k])) return null; |
|
return $this->seq[ ($this->key=$k) ]; |
|
} |
|
/** |
|
* offsetExists applies TSequence's Numeric Key Formatting to provided key |
|
* and returns true if the element exists and it not null (using isset()); |
|
* @param mixed $k key of desired element to fetch |
|
* @return bool whether key is set to a non-null value. |
|
*/ |
|
function offsetExists($k) { |
|
return isset( $this->seq[ is_numeric($k)?(string)($k+0):"$k" ] ); |
|
} |
|
/** |
|
* exists applies TSequence's Numeric Key Formatting to provided key |
|
* and returns true if the element exists, whether set to null or not. |
|
* @param mixed $k key of desired element to fetch |
|
* @return bool whether key exists, whether set to null or not. |
|
*/ |
|
function exists($k) { |
|
return array_key_exists(is_numeric($k)?(string)($k+0):"$k", $this->seq); |
|
} |
|
/** |
|
* offsetUnset is called when unset is applied to the object at a location |
|
* specifed within square brackets. It applies TSequence's Numeric Key |
|
* Formatting to provided key before performing unset. |
|
* @param mixed $k key of desired element to fetch |
|
* @return object returns $this |
|
*/ |
|
function offsetUnset($k) {unset($this->seq[is_numeric($k)?(string)($k+0):"$k"]);} |
|
|
|
|
|
/** |
|
* merge() behaves similar to array_merge() only each element being merged in |
|
* is added as if assigned with the subscript and offsetSet, That means if |
|
* you have auto_incr set you won't be overwritting anything, otherwise you |
|
* would clobber any already occupied keys. |
|
* |
|
* There is normally only one argument, an array, but if it is not an array, |
|
* it is pushed to the array as if assigned the []= ( possibly using $uto_incr |
|
* and/or $key ) and if you have more than one argument they are pushed |
|
* one by one in this same manner. |
|
* @param mixed $a an array to merge in, value to be pushed or the first of many. |
|
* @return object returns $this |
|
*/ |
|
function merge($a=array()) { |
|
if (!$this->seq) return $this->seq(func_get_args()); |
|
$m = 'getArrayCopy'; // to de-clutter complex elseif conditional below |
|
if (($argc=func_num_args())===0) return $this; |
|
|
|
if ($argc>1) $a=func_get_args(); |
|
elseif ( is_array($a) || (method_exists($a,$m) && $a=$a->$m() || true ) ) |
|
if (!$a) return $this; |
|
else $a = array($a); |
|
|
|
// non-empty array neeeds sanitizing. |
|
foreach ($a as $k=>$v) { if (!$this->offsetSet($k, $v)) return false; } |
|
return $this;# skip setting $this->key since offsetSet took care of it |
|
} |
|
/** |
|
* ksort() sorts the array by key. |
|
* @return object returns $this |
|
*/ |
|
function ksort() { ksort($this->seq); return $this; } |
|
/** @ignore */ |
|
function __toString() { ksort($this->seq); return var_export($this->seq, true); } |
|
/** @ignore */ |
|
function getArrayCopy(){ ksort($this->seq); return $this->seq; } |
|
/** @ignore */ |
|
function &getArrayRef(){ ksort($this->seq); return $this->seq; } |
|
/** @ignore */ |
|
function unserialize($serialized) { |
|
$this->seq = unserialize($serialized); ksort($this->seq); |
|
} |
|
/** @ignore */ |
|
function serialize() { ksort($this->seq); return serialize($this->seq); } |
|
/** @ignore */ |
|
function getIterator() { ksort($this->seq); return new ArrayIterator($this->seq); } |
|
|
|
/** |
|
* reIndex ksorts the sequence and changes the numeric component of |
|
* each key to create an even spread set by an increment (argument 2), |
|
* which defaults to 1. |
|
* |
|
* The first and third arguments set the first and last index, respectively. |
|
* They default to 0 and 'auto', respectively. 'auto' simply means that the |
|
* last index will be whatever it needs to be if starting at 0 and incrementing |
|
* by adding 1 for each key. |
|
* |
|
* The first and third args can be set to 'auto' or 'retain', which means |
|
* they will retain their current index/numeric value. |
|
* |
|
* The second argument sets the increment value and can be a numeric or 'auto', |
|
* but note that only one argument can be 'auto' at a given time. |
|
* |
|
* All indexes are integers unless the fourth argument is set to true or |
|
* a non-integer numeric is found in the first three arguments. |
|
* |
|
* Valid arguments: |
|
* #1 #2 NUMBER, 'auto', 'retain' or NUMBER |
|
* #3 NUMBER, NUMBER, 'auto' |
|
* #4 #5 'retain', 'auto', 'retain' or NUMBER |
|
* #6 'retain', NUMBER, 'auto' |
|
* #7 #8 'auto', NUMBER, 'retain' or NUMBER |
|
* @param mixed $from can be a numeric, 'retain' or 'auto' |
|
* @param mixed $incr can be a numeric or 'auto' |
|
* @param mixed $to can be a numeric, 'retain' or 'auto' |
|
* @param bool $allow_flt sets whether indexes can be floating point |
|
* @return object returns $this |
|
*/ |
|
function reIndex($from=0, $incr=1, $to='auto', $allow_flt=null) { |
|
ksort($this->seq); reset($this->seq); $reset = key($this->seq); |
|
if (($argc=func_num_args())!==0) # argument parsing |
|
{ |
|
if ($allow_flt===null) { |
|
$allow_flt = ( !is_int($from) && is_numeric($from) |
|
|| $argc>1 && !is_int($incr) && is_numeric($incr) |
|
|| $argc>2 && !is_int($to) && is_numeric($to) ); |
|
} |
|
end($this->seq); $end = key($this->seq); |
|
$c = count($this->seq); # we might need this |
|
|
|
# PHP will warn for non-numeric so we can skip some is_numeric calls |
|
if ($from===0 || is_numeric($from)){ # $incr is ignored/overwritten |
|
if ($incr==='auto'){ |
|
if ($to==='retain') $to = $end; #1, else #2 w/numeric $to |
|
$incr = (($to = $allow_flt ? $to : (int)$to) - $from) / $c; |
|
}#else#3 w/numeric$incr |
|
} |
|
elseif ($from==='retain'){ |
|
$from = $allow_flt ? $reset : (int)$reset; |
|
if ($incr==='auto'){ |
|
if ($to==='retain') $to=$end;#4, else#5 w/numeric $to |
|
$incr = (($to = $allow_flt ? $to : (int)$to) - $from) / $c; |
|
}#else #6 skipping is_numeric($incr) |
|
} |
|
elseif ($from==='auto'){ |
|
if ($to==='retain') $to = $end;#7 w/numeric$incr, else#8 w/numeric$to |
|
$from = ($to = $allow_flt ? $to : (int)$to) - $incr * $c; |
|
} |
|
else return trigger_error("ReIndex invalid 1st arg",E_USER_ERROR)&&false; |
|
|
|
if (!$allow_flt) $incr = (int)$incr; |
|
} |
|
/* run the action ...................................*/ |
|
$seq = $backup = $this->seq; $this->seq=array(); #sort & backup |
|
$k = $reset; $i = $from; |
|
|
|
$str = preg_match(self::$ksplit,$k,$c) && isset($c[2]) ? $c[2]:''; |
|
$this->seq["$i".$str] = $reset_val = $seq[$k];# $reset_val is backup |
|
unset($seq[$reset]); $i = $i + $incr; |
|
|
|
foreach ($seq as $k=>$v){ |
|
if ($to!=='auto' && $k===$end) $i = $to; |
|
$str = preg_match(self::$ksplit,$k,$c) && isset($c[2]) ? $c[2]:''; |
|
if ( isset($this->seq[ ($new_k="$i".$str) ]) ) { |
|
$this->seq = array($reset=>$reset_val)+$seq; |
|
throw new Exception("[$k] would result in reIndex overflow"); |
|
} |
|
$this->seq[$new_k] = $seq[$k]; |
|
$i = $i + $incr; |
|
} |
|
return $this; |
|
} |
|
/** |
|
* Searches for the key(s) for a given value, provided as the first argument, |
|
* in the sequence array. Does various types of searches depending on |
|
* the value of the second argument, $mode: |
|
* |
|
* 'key' - One-dimensional search, returns key |
|
* 'num' - One-dimensional search, returns key's numeric prefix |
|
* 'int' - One-dimensional search, returns key's numeric prefix convert to int. |
|
* 'tag' - One-dimensional search, returns key's non-numeric portion, including space |
|
* |
|
* There are also a few recursive search mode, where a single result of |
|
* each is an array representing the 'path' of keys approaching the value. |
|
* The first element is the top key, next is the subarray's key, etc. If no |
|
* match if found, false is returned. Note that 'defualt depth' below refers |
|
* to the value of the object's $depth property. If depth is 1, the match would |
|
* an array of one element, the top level key or 2 element, the top level key |
|
* and the first 'deep' key. |
|
* |
|
* 'deep' - recursive search with default depth. |
|
* '8deep' - same as above but with custom depth of 8 - array would be 1-9 keys |
|
* '8', 8 - same as above since 'deep' is the default recursive mode. |
|
* 'all' - returns all (as array of arrays), not just the first match, using default depth. |
|
* '3 all' - same as above but with custom depth of 3. |
|
* |
|
* If $mode is anything else, a one-dimensional search is performed and the |
|
* matched key is chopped up and return as an array with 'num','idx','space','tag' |
|
* |
|
* @param mixed $val is the value who's key we are searching for. |
|
* @param mixed $mode see above |
|
* @param bool $strict if true, run strict search |
|
* @return object returns $this |
|
*/ |
|
function search($val, $mode=null, $strict=true) { |
|
if ($mode===null) $mode = $this->search_mode; |
|
if (strpos($mode, 'all')!==false) { |
|
$depth = ($depth=(int)$mode) ? $depth : $this->depth; |
|
$r = function ($val, $a, $d, $s, $l=0, &$path=[], &$keys=[], &$i=0) use (&$r) { |
|
foreach($a as $k=>$v) { |
|
$path[] = $k; |
|
if (!$s && $v==$val || $v===$val) $keys[$i++] = $path; |
|
elseif ($l<$d && (is_array($v) /*|| method_exists($v,'getIterator')*/)) |
|
$r($val, $v ,$d, $s, $l, $path, $keys, $i); |
|
array_pop($path); |
|
} |
|
if ($l!==0) return $keys ? $keys : false; |
|
return array_values( $keys ); |
|
}; |
|
return $r($val, $this->seq, $depth, $strict); |
|
} |
|
elseif (is_numeric($mode) || strpos($mode, 'deep')!==false) { |
|
$depth = ($depth=(int)$mode) ? $depth : $this->depth; |
|
$r = function ($val, $a, $d, $s, $l=0, $keys=array()) use (&$r) { |
|
foreach($a as $k=>$v) { |
|
if (!$s && $v==$val || $v===$val) { $keys[]=$k; return $keys; } |
|
elseif ($l<$d && (is_array($v) /*|| method_exists($v,'getIterator')*/)) { |
|
$keys[]=$k; |
|
if ($keys=$r($val, $v ,$d, $s, $l+1, $keys)) return $keys; |
|
} |
|
} |
|
return false; |
|
}; |
|
return $r($val, $this->seq, $depth, $strict); |
|
} |
|
|
|
if (($k=array_search($val,$this->seq,$strict))===false) return false; |
|
else $this->key = $k; // i guess |
|
|
|
if ($mode==='key') return $k; |
|
elseif($mode==='num') { preg_match(self::$kparse,$k,$c); return $c[1]; } |
|
elseif($mode==='idx') { preg_match(self::$kparse,$k,$c); return (int)$c[1]; } |
|
elseif($mode==='tag') { |
|
preg_match(self::$kparse,$k,$c); return isset($c[3]) ? $c[3] : null; |
|
} else { |
|
preg_match(self::$kparse, $k, $c); |
|
$c = array_pad($c, 4, null); |
|
return array('num'=>$c[1],'idx'=>(int)$c[1],'space'=>$c[2],'tag'=>$c[3]); |
|
} |
|
} |
|
/** @ignore */ |
|
function count() { return count( $this->seq ); } |
|
/** @ignore */ |
|
function length() { return count( $this->seq ); } |
|
/** @ignore */ |
|
function show() { var_export($this->seq); return $this;} |
|
/** @ignore */ |
|
protected function kTypeErr($k, $arg2=E_USER_ERROR) { |
|
if (is_array($arg2)) : $this->seq=$arg2; $flag=E_USER_WARNING; |
|
elseif (is_integer($arg2)): $flag=$arg2; endif; |
|
debug_print_backtrace(); |
|
return trigger_error(get_called_class()." keys must be or start with number" |
|
. ($k ? ", received '$k'" :''), $flag) && false; |
|
} |
|
} |
|
class Sequence implements Serializable, IteratorAggregate, ArrayAccess, Countable |
|
{ |
|
use TSequence; |
|
|
|
function __construct($a1=null, $a2=null) |
|
{ |
|
if (($argc=func_num_args())===1): |
|
call_user_func(array($this,'seq'), $a1); |
|
elseif ($argc===2): |
|
call_user_func(array($this,'seq'), $a1, $a2); |
|
elseif ($argc>2): |
|
call_user_func_array(array($this,'seq'), func_get_args()); |
|
endif; |
|
} |
|
} |
|
$seq = new Sequence; |
|
$seq->seq = array( |
|
0=>array(1=>array(2=>'depth2') ), |
|
'1.4' => 0, |
|
'1depth1'=>array('depth1'), |
|
'2arr1'=>array( |
|
'arr2'=>array( |
|
'arr3'=>array('3findme'), |
|
), |
|
), |
|
'3findme1'=>array('findme'), |
|
); |
|
print_vals($seq->search('depth1')); # ['depth1', 0] |
|
print_vals($seq->search('depth1', 0)); # [ ] |
|
print_vals($seq->search('depth1', 0, false));# ['1.4'] beware! non-strict |
|
print_vals($seq->search('depth2')); # [ ] default depth is 1 |
|
print_vals($seq->search('depth2', 2)); # [0,1,2] depth now 2 |
|
print_vals($seq->search('findme', 3)); # ['arr1','arr2','arr3',0] |
|
|
|
// note that the value 'findme' occured twice but we only caught one |
|
print_vals($seq->search('findme', '3 all')); # ['arr1','arr2','arr3',0] |
|
|
|
/* a third argument containing 'all' and optionally starting with an int |
|
* will return an array of recursive_search values: |
|
[ |
|
['arr1', 'arr2', 'arr3', 0], |
|
['findme1', 0], |
|
] |
|
*/ |
|
|
|
//////////////////////////////////////////////////////////////////////////////// |
|
// just to display demo: |
|
function print_vals($arr) { |
|
if (!$arr) { echo "[ ]\n"; return; } |
|
foreach ($arr as $v) { if (is_array($v)) {$run_sub=true; break;} } |
|
if (isset($run_sub)) { |
|
$subs = function ($arr) { |
|
echo "[\n"; |
|
foreach ($arr as $sub) { |
|
if (!$sub) { echo "[ ]\n"; return; } |
|
$s = is_int($sub[0]) ? "\t[${sub[0]}": "\t['${sub[0]}'"; unset($sub[0]); |
|
foreach($sub as$v) $s = is_int($v) ? "$s, $v" : "$s, '$v'"; |
|
echo "$s],\n"; |
|
} |
|
echo "]\n"; |
|
}; |
|
return $subs($arr); |
|
} |
|
$s = is_int($arr[0]) ? "[${arr[0]}": "['${arr[0]}'"; unset($arr[0]); |
|
foreach($arr as$v) $s = is_int($v) ? "$s,$v" : "$s,'$v'"; |
|
echo "$s]\n"; |
|
} |
offsetSet variation 1