Recently I was faced with a coding challenge where I had to build a simple trie in php, I managed to do it using php and foreach loops but I'm not happy with the code itself (seems not solid as it should be) so I'm trying to implement it using php's iterators.
So, I have a complex array ( a trie ), for example:
array(
'a' => array(),
'b' => array(
'a' => array(
'c' => array(
'o' => array(
'n' => array()
)
)
)
),
'x' => array(
'x' => array(
'x' => array()
)
)
);
And I want to check if 'bacon' it's a word stored on this trie, the process to find it should be by iterating through the array and check if each node it's nested and exists, for instance: I need in the root the element with key 'b', then inside the array array['b'] , I need to check if I there is array['b']['a'] , then ['b']['a']['c'] and so on.
With a foreach loop I was able to do so by passing the new array by reference and check the keys. Now using a iterator it seems I'm hammering the code a bit ( and the fact that when doing foreachs php copies the array, makes me think that this solution might use a lot more memory than using iterators).
So the code until now it's a while loop that has a condition finished that stops on fail (the current array doesn't have the key I'm searching) or success ( the word it's complete ):
// OUTSIDE THE LOOP
$finished = false;
$string = 'bacon';
$string = str_split($string);
$queue = new SplQueue();
// Enqueue all the letters to the queue -> skipping this because it's boring
// FIRST WHILE LOOP
$iterator = new ArrayIterator($array);
$iterator->key(); // No match with queue -> check next key
// SECOND WHILELOOP
$iterator->next();
$iterator->key(); // Matches with the key I want do dequeue (B),
$next = new ArrayIterator($array[$iterator->key()]);
$queue->dequeue();
// THIRD WHILE LOOP
$next->key(); // Match [A] -> create new iterator
$next = new ArrayIterator($next[$next->key()]);
$queue->dequeue();
// 4TH WHILE LOOP
$next->key(); // Match [C] -> create new iterator
$next = new ArrayIterator($next[$next->key()]);
$queue->dequeue();
// 5TH WHILE LOOP
$next->key(); // Match [O] -> create new iterator
$next = new ArrayIterator($next[$next->key()]);
$queue->dequeue();
// 5TH WHILE LOOP
$next->key(); // Match [N]
$next = new ArrayIterator($next[$next->key()]);
$queue->dequeue(); // queue empty, throw success
So, up until now it's this I have, but the fact I'm creating a new ArrayIterator on each loop it's bothering me, so I was hoping to hear if someone has a better solution to this problem.
Thanks in advance.
Here is code for a recursive algorithm which will iterate any number of levels:
<?php
$trie = array(
'a' => array(),
'b' => array(
'a' => array(
'c' => array(
'o' => array(
'n' => array()
)
)
)
),
'x' => array(
'x' => array(
'x' => array()
)
)
);
/**
* #param string $word
* #param array $array
* #param int [$i = 0]
*/
function findWord($word, $array, $i = 0)
{
$letter = substr($word, $i, 1);
if (isset($array[$letter])) {
if ($i == strlen($word) - 1) {
return true;
} else if ($i < strlen($word)) {
return findWord($word, $array[$letter], $i + 1);
}
}
return false;
}
if (findWord('bacon', $trie)) {
die("Did find word.");
} else {
die("Didn't find word.");
}
Here is code for a iterative algorithm which will iterate any number of levels and should be memory and cpu efficient:
<?php
$trie = array(
'a' => array(),
'b' => array(
'a' => array(
'c' => array(
'o' => array(
'n' => array()
)
)
)
),
'x' => array(
'x' => array(
'x' => array()
)
)
);
/**
* #param string $word
* #param array $array
*/
function findWord($word, $array)
{
$tmpArray = $array;
for ($i = 0; $i < strlen($word); $i++)
{
$letter = substr($word, $i, 1);
if (isset($tmpArray[$letter])) {
if ($i == strlen($word) - 1) {
return true;
} else {
$tmpArray = $tmpArray[$letter];
}
} else {
break;
}
}
return false;
}
if (findWord('bacon', $trie)) {
die("Did find word.");
} else {
die("Didn't find word.");
}
This is a good challenge to solve this problem using iterators. Although I think that iterators are great, but they force you to think in terms of iterative approach. While for some problems it's ok, but for tasks like you described it makes more sense to use recursion.
So, I think that you should accept the answer of #cjohansson. As it is readable and understandable.
But just as a prove of concept here's my solution using RecursiveIteratorIterator. We have to extend this class and alter it a bit to suit our needs and also to reduce the number of unnecessary iterations:
class TrieRecursiveIteratorIterator extends RecursiveIteratorIterator
{
protected $word;
public function __construct(
$word,
Traversable $iterator,
$mode = RecursiveIteratorIterator::LEAVES_ONLY,
$flags = 0
) {
$this->word = str_split($word);
parent::__construct($iterator, $mode, $flags);
}
public function next()
{
if ($this->currentLetterMatched()) {
$this->updatePrefix();
$this->setPrefixed();
}
parent::next();
}
protected $prefix = [];
protected function updatePrefix()
{
$this->prefix[$this->getDepth()] = $this->key();
}
protected $prefixed = [];
protected function setPrefixed()
{
$this->prefixed = $this->current();
}
public function valid()
{
if (
$this->getDepth() < count($this->prefix)
|| count($this->word) === count($this->prefix)
) {
return false;
}
return parent::valid();
}
public function callHasChildren()
{
if ($this->currentLetterMatched()) {
return parent::callHasChildren();
}
return false;
}
protected function currentLetterMatched()
{
return isset($this->word[$this->getDepth()])
&& $this->key() === $this->word[$this->getDepth()];
}
public function testForMatches()
{
foreach ($this as $_) {
}
return $this;
}
public function getPrefix()
{
return implode('', $this->prefix);
}
public function getPrefixed()
{
return $this->prefixed;
}
public function matchFound()
{
return ($this->word === $this->prefix);
}
public function exactMatchFound()
{
return ($this->word === $this->prefix) && empty($this->prefixed);
}
public function prefixMatchFound()
{
return ($this->word === $this->prefix) && !empty($this->prefixed);
}
}
Then we can do following:
$iterator = new TrieRecursiveIteratorIterator(
$word,
new RecursiveArrayIterator($trie),
RecursiveIteratorIterator::SELF_FIRST
);
$iterator->testForMatches();
After that, we can ask our $iterator different things, such as:
If match was found: $iterator->matchFound();
If exact match was found: $iterator->exactMatchFound();
If there are words that prefixed with given word: $iterator->prefixMatchFound();
Get prefix itself (when either of matches were found the prefix will be equal to given word): $iterator->getPrefix();
Get endings prefixed with given word: $iterator->getPrefixed().
Here is working demo.
So as you can see this realization is not as straight forward as recursion one. And while I am a big fan of iterators and SPL usage, but this is not a silver bullet and you should chose tools that fits your current needs better.
Also, this is outside the domain, but my class violates Single responsibility principle. This was intentional for the sake of simplicity. In real life there would be the other class wich will use our iterator as dependency.
Related
I have object of class $values like:
Array
(
[0] => App\ValueObject\Features Object
(
[feature:App\ValueObject\Features:private] => CONNECT_NETWORKS_ON_SIGN_UP
[value:App\ValueObject\Features:private] => 1
)
[1] => App\ValueObject\Features Object
(
[feature:App\ValueObject\Features:private] => SHOW_BILLING
[value:App\ValueObject\Features:private] => 1
)
[2] => App\ValueObject\Features Object
(
[feature:App\ValueObject\Features:private] => FEATURE_FLAGS
[value:App\ValueObject\Features:private] => 'activity'
)
)
All array keys are returning boolean type value expect one, which returns string value.
My result with the code:
$arrays = array_map(
function($value) { return [strtolower((string) $value->getFeature())]; },
iterator_to_array($values)
);
return array_merge(...$arrays);
returns list of feature names like:
"features": [
"connect_networks_on_sign_up",
"show_billing",
"feature_flags"
]
What I want to edit is that for the last one we write its value NOT feature name ($value->getValue())
I am assuming that using in_array() PHP function would be the best approach here but I can't find a way to use it within my current method.
Tried with foreach() loop but nothing happens, like it's something wrong:
$features = [];
foreach ($values as $value)
{
$setParam = $value->getFeature();
if ($value == 'FEATURE_FLAGS') {
$setParam = $value->getValue();
}
$features[] = strtolower((string) $setParam);
}
return $features;
Can someone help?
Thanks
You should probably operate on the feature code FEATURE_FLAGS, rather than assuming that the last feature in the array always contains the flags. Using your existing code, that could be as simple as:
$arrays = array_map(
function($value)
{
/*
* If the current Features object has the feature code FEATURE_FLAGS,
* return the value itself, otherwise return the feature code in lowercase
*/
return ($value->getFeature() == 'FEATURE_FLAGS') ? [$value->getValue()]:[strtolower((string) $value->getFeature())];
},
iterator_to_array($values)
);
If you want to define an array of feature codes that you need to treat this way, you can define it internally in the callback, but it is probably a better idea to define it externally. You can then pass it into the callback with use
/*
* Define an array of feature codes that we want to return
* values for
*/
$valueCaptureFeatures = ['FEATURE_FLAGS'];
$arrays = array_map(
function($value) use ($valueCaptureFeatures) // <-- Put our $valueCaptureFeatures in the scope of the callback
{
/*
* If the current Features object has a feature code in the $valueCaptureFeatures array,
* return the value itself, otherwise return the feature code in lowercase
*/
return (in_array($value->getFeature(), $valueCaptureFeatures)) ? [$value->getValue()]:[strtolower((string) $value->getFeature())];
},
iterator_to_array($values)
);
Working example:
// Mock the Features class
class Features
{
private $feature;
private $value;
public function __construct($feature, $value)
{
$this->feature = $feature;
$this->value = $value;
}
public function getFeature()
{
return $this->feature;
}
public function setFeature($feature): void
{
$this->feature = $feature;
}
public function getValue()
{
return $this->value;
}
public function setValue($value): void
{
$this->value = $value;
}
}
// Mock an iterator with test Feature instances
$values = new ArrayIterator( [
new Features('CONNECT_NETWORKS_ON_SIGN_UP', 1),
new Features('SHOW_BILLING', 1),
new Features('FEATURE_FLAGS', 'activity')
]);
/*
* Define an array of feature codes that we want to return
* values for
*/
$valueCaptureFeatures = ['FEATURE_FLAGS'];
$arrays = array_map(
function($value) use ($valueCaptureFeatures) // <-- Put our $valueCaptureFeatures in the scope of the callback
{
/*
* If the current Features object has a feature code in the $valueCaptureFeatures array,
* return the value itself, otherwise return the feature code in lowercase
*/
return (in_array($value->getFeature(), $valueCaptureFeatures)) ? [$value->getValue()]:[strtolower((string) $value->getFeature())];
},
iterator_to_array($values)
);
$output = array_merge(...$arrays);
$expectedResult = [
'connect_networks_on_sign_up',
'show_billing',
'activity'
];
assert($output == $expectedResult, 'Result should match expectations');
print_r($output);
I have this example array.
$data = new stdClass();
$data->foo = [
'foo1' => &$data,
'foo2' => 23,
];
$data->bar = new stdClass();
$data->nar->object = [
'bar1' => &$data->bar,
'bar2' => 43,
];
I want to parse this to:
$data = new stdClass();
$data->foo = [
'foo1' => "RECURSION DETECTED",
'foo2' => 23,
];
$data->bar = new stdClass();
$data->nar->object = [
'bar1' => "RECURSION DETECTED",
'bar2' => 43,
];
I need it, because json_encode can't encode data when recursion is detected.
I tried so many times and in different ways, I did a lot of research, but I did not find anything to really help me.
My last attempt was:
function _stack(&$object, &$stack = [], $key = 'original')
{
if (isObjectOrArray($object)) {
if (!in_array($object, $stack, true)) {
if (is_object($object)) {
$stack[$key] = &$object;
}
foreach ($object as $key => &$value) {
_stack($value, $stack, $key);
}
}
}
return $stack;
}
function _remove($object, $stack, $objectO = false, $key = 'original')
{
/**
* #var $objectO false | object
*/
if (!$objectO) {
$objectO = $object;
}
if (isObjectOrArray($object)) {
foreach ($object as $prop => $value) {
if (is_object($objectO)) {
if (in_array($object->{$prop}, $stack, true) && $prop !== $key) {
$objectO->{$prop} = "RECURSION DETECTED";
} else {
$objectO->{$prop} = _remove($object->{$prop}, $stack, $objectO->{$prop}, $prop);
}
} else {
if (in_array($object[$prop], $stack, true) && $prop !== $key) {
$objectO[$prop] = "RECURSION DETECTED";
} else {
$objectO[$prop] = _remove($object[$prop], $stack, $objectO[$prop], $prop);
}
}
}
}
return $objectO;
}
First i crate an stack with original objects (not reference / pointer).
The key is passed to the function, within itself in recursion, so I know exactly where recursion meets the original object. I need it so I can then tell what the pointer is and what the original object is.
After create stack i run the same looping, but the current value inside foreach statement is an object and he is inside stack and the current key is diferent of current key pass to the function call, the reference / pointer is breaked.
Array
(
[foo1] => RECURSION DETECTED
[foo2] => 23
)
But at the end of all function calls I get only:
RECURSION DETECTED
I am still looking at another way since this is interesting, but it is easy to replace the reference pointer in a serialized string and then unserialize it:
$data = unserialize(preg_replace('/R:\d+/', 's:18:"RECURSION DETECTED"', serialize($data)));
Another option for PHP >= 7.3.0 is exporting and forcing it to break the references. var_export will complain about recursion, however it will happily display it with the references replaced with NULL. var_export has a second argument to return the output instead of displaying, but this doesn't work with recursion so I buffered and captured the output.
ob_start();
#var_export($data);
$var = ob_get_clean();
eval("\$data = $var;");
For PHP < 7.3.0 you can use the above code with your own class that implements __set_state instead of stdClass:
class myClass {
public static function __set_state($array) {
$o = new self;
foreach($array as $key => $val) {
$o->$key = $val;
}
return $o;
}
}
$data = new myClass();
I have an user input like this: $_POST['multi']['dim']['form'] = 1.213.
I want to validate this and at the moment I use this function:
public function checkFloat($number, $min = null, $max = null)
{
$num = filter_var($number, FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND);
if($num === false || ($min !== null && $num < $min) || ($max !== null && $num > $max))
return false;
return $num;
}
But befor calling this function I have to check first that $_POST['multi']['dim']['form'] is set.
So every call looks like
if(isset($_POST['multi']['dim']['form']) && ($num = checkFloat($_POST['multi']['dim']['form']) !== false))
{
// do something here
}
If I do not check first, if the variable is set, PHP will throw a notice.
I noticed the PHP function filter_input, but it seems that this is not working with multidimensional $_POST.
I thought about a wrapper like
function checkFloat($names_of_the_fields,$min,$max)
{
// check if $_POST[$name][$of][$the][...] is set
// make the validation
}
But I'm not sure how to pass the $name_of_the_fields.
Here I thought of an array $arr['key1']['key2'][...]. but since I not know how deep this is, i have to run a lot is_array checks. Or I pass an array like $arr = ['key1','key2',...].
Is there a nice and clean way to do this? Should I ignore the notice?
Or should I go on with if(isset.. && checkFloat...)?
Changing the form and using eval() is not an option.
Thanks in advance
EDIT 1:
is_array($var) is not that slow, if $var is setted. So it would be ok, if I use a function that checks structure. But the question is still, if this is a good way, or if there is a better (maybe faster) way to do this.
How about this? You'll like it :D
function filter_input_simple($type, $name, $filter = FILTER_DEFAULT, $options = FILTER_REQUIRE_SCALAR) {
static $vars;
if (!$vars) {
$vars = array(
INPUT_GET => filter_input(INPUT_SERVER, 'QUERY_STRING'),
INPUT_POST => file_get_contents('php://input'),
INPUT_COOKIE => filter_input(INPUT_SERVER, 'HTTP_COOKIE'),
);
$s = array('&', '&', ';[; ]++');
foreach ($vars as $t => $var) {
$tmp = array();
foreach (preg_split("#{$s[$t]}#", $var, -1, PREG_SPLIT_NO_EMPTY) as $i) {
list($k, $v) = explode('=', $i, 2) + array(1 => '');
$tmp[urldecode($k)] = urldecode($v);
}
unset($tmp['']);
$vars[$t] = $tmp;
}
$vars += array(INPUT_REQUEST =>
$vars[INPUT_COOKIE] + $vars[INPUT_POST] + $vars[INPUT_GET]
);
}
$type = (int)$type;
$name = filter_var($name);
if (!isset($vars[$type][$name])) {
return null;
}
return filter_var($vars[$type][$name], $filter, $options);
}
Usage:
// This function does not use extracted values as $_GET,
// so put QueryString on your browser for testing.
//
// ?foo[bar][validated_float]=1,234.213
$options = array(
'options' => array(
'min_range' => 1234,
'max_range' => 1235,
),
'flags' => FILTER_FLAG_ALLOW_THOUSAND,
);
var_dump(filter_input_simple(INPUT_GET, 'foo[bar][validated_float]', FILTER_VALIDATE_FLOAT, $options));
After some thinking and testing I came up with this:
I splitted each validation function up into two functions and added a function that checks if an array key exists.
validate[Type]Var($val, $options): checks if $val contains a valid value. (no changes here)
validate[Type]Input(&$inputArr,$keys,$options): calls getArrayValue(&array,$keys) and if the key exists, it calls validate[Type]Val() to validate the value.
In detail:
public function getArrayValue(&array,$keys)
{
$key = array_shift($keys);
if(!isset($array[$key]))
return null;
if(empty($keys))
return $array[$key];
return $this->getArrayValue($array[$key],$keys);
}
public function validateTypeInput(&$inputArr, $keys, $options = [])
{
$value = $this->getArrayValue($inputArr, $keys);
if(isset($value))
return $this->validateTypeVal($value,$options);
else
return null; // or return anything else to show that the value was invalid
}
The function can be called by
ValidationClass->validateTypeInput($_POST,['key1','key2','key3'],$options);
to validate $_POST['key1']['key2']['key3'].
Notice: I wrote an validateTypeInput for each type, because some of them are different. Eg. if you validate checkbox input, you dont want a not setted input to be considered invalid.
It won't validate the structure of the data passed to it, but it will ensure that each item passes the checkFloat function:
function validate_info($info) {
if (is_array($info)) {
// $info is an array, validate each of its children
foreach ($info as $item) {
// If item is invalid, stop processing
if (validate_info($item) === false) { return false; }
}
// If each $item was valid, then this $info is valid.
return true;
} else {
// $info is a single item
return checkFloat($info, [YOUR_MIN_VAL], [YOUR_MAX_VAL]);
}
}
The function will return true if every item in $info returns true for checkFloat. If any value in $info returns false, then validate_info will return false. This function uses recursion to check the whole array, regardless of structure.
edit: This function does use the is_array function, but you seem to be mistakenly thinking that the is_array function is slow. The is_array function runs in constant time, which can be verified by checking the PHP source code.
Class Definition:
/**
* Object for filter_input_array_recursive().
*/
class FilterObject {
private $filter;
private $options;
/**
* Constructor.
*
* #param int $filter same as ones for filter_input().
* #param mixed $options same as ones for filter_input().
*/
public function __construct($filter = FILTER_DEFAULT, $options = FILTER_REQUIRE_SCALAR) {
$this->filter = $filter;
$this->options = $options;
}
public function getFilter() {
return $this->filter;
}
public function getOptions() {
return $this->options;
}
}
Function Definition:
/**
* Apply filter_input() recursively.
*
* #param int $type INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_REQUEST are supported.
* #param array $filters Multi-demensional array, which contains FilterObject for each leaf.
* #return array
*/
function filter_input_array_recursive($type, array $filters) {
static $recursive_static;
static $flag_match;
static $is_not_array;
static $filter_array;
static $types;
if (!$flag_match) {
/* initialize static variables */
$types = array(
INPUT_GET => $_GET,
INPUT_POST => $_POST,
INPUT_COOKIE => $_COOKIE,
INPUT_REQUEST => $_REQUEST,
);
$flag_match = function ($v, $f) {
return (int)(isset($v['flags']) ? $v['flags'] : $v) & $f;
};
$is_not_array = function ($v) {
return !is_array($v);
};
$filter_array = function ($v) {
return !is_array($v) ? $v : false;
};
}
$recursive = $recursive_static;
if (!$recursive) {
/* only for first loop */
$type = (int)$type;
if (!isset($types[$type])) {
throw new \InvalidArgumentException('unknown super global var type');
}
$var = $types[$type];
$recursive_static = true;
} else {
/* after first loop */
$var = $type;
}
$ret = array();
foreach ($filters as $key => $value) {
$isset = isset($var[$key]);
if (is_array($value)) {
// apply child filters
$ret[$key] = filter_input_array_recursive($isset ? $var[$key] : array(), $value);
} else {
if (!($value instanceof FilterObject)) {
// create default FilterObject for invalid leaf
$value = new FilterObject;
}
$filter = $value->getFilter();
$options = $value->getOptions();
// check if key exists...
// true -> apply filter_var() with supplied filter and options
// false -> regard as null
try {
$ret[$key] = $isset ? filter_var($var[$key], $filter, $options) : null;
} catch (Exception $e) {
$recursive_static = false;
throw $e;
}
if ($flag_match($options, FILTER_FORCE_ARRAY | FILTER_REQUIRE_ARRAY)) {
// differently from filter_input(),
// this function prevent unexpected non-array value
if (!is_array($ret[$key])) {
$ret[$key] = array();
}
// differently from filter_input(),
// this function prevent unexpected multi-demensional array
if ($flag_match($options, FILTER_FORCE_ARRAY)) {
// eliminate arrays
$ret[$key] = array_filter($ret[$key], $is_not_array);
} else {
// change arrays into false
$ret[$key] = array_map($filter_array, $ret[$key]);
}
}
}
}
if (!$recursive) {
/* only for first loop */
$recursive_static = false;
}
return $ret;
}
Usage:
$_POST['foo']['bar']['validated_float'] = '1,234.213';
$_POST['foo']['forced_1d_array'] = array('a', array('b'), 'c');
$_POST['foo']['required_1d_array'] = array('a', array('b'), 'c');
$_POST['foo']['required_scalar'] = array('a', array('b'), 'c');
When inputs are like this,
var_dump(filter_input_array_recursive(INPUT_POST, array(
'foo' => array(
'bar' => array(
'validated_float' => new FilterObject(
FILTER_VALIDATE_FLOAT,
array(
'options' => array(
'min_range' => 1234,
'max_range' => 1235,
),
'flags' => FILTER_FLAG_ALLOW_THOUSAND,
)
),
),
'forced_1d_array' => new FilterObject(FILTER_DEFAULT, FILTER_FORCE_ARRAY),
'required_1d_array' => new FilterObject(FILTER_DEFAULT, FILTER_REQUIRE_ARRAY),
'required_scalar' => new FilterObject,
),
)));
this snippet will output...
array(1) {
["foo"]=>
array(4) {
["bar"]=>
array(1) {
["validated_float"]=>
float(1234.213)
}
["forced_1d_array"]=>
array(2) {
[0]=>
string(1) "a"
[2]=>
string(1) "c"
}
["required_1d_array"]=>
array(3) {
[0]=>
string(1) "a"
[1]=>
bool(false)
[2]=>
string(1) "c"
}
["required_scalar"]=>
bool(false)
}
}
I have a class that works with a nested array and I want to perform operations on a value somewhere in that nested array, given an arbitrary set of indexes.
I'm using a reference to refer to the value that is being manipulated.
class Test {
function __construct(){
// just an example of a multi dimensional array to work with
$this->data = array(
array( 10, 100, 1000),
array( 20, 200, 2000),
array(
array( 30, 300, 3000),
array( 40, 400, 5000),
array( 50, 400, 5000)
)
);
}
function set_reference( $indexes ){
// set reference to a value somewhere inside $this->data
$this->current = &$this->data[$not][$sure]; // but how
return $this;
}
function add_three(){
$this->current += 3;
return $this;
}
function render(){
var_dump( $this->current );
return $this;
}
}
This example would then be able to work something like this.
$test = new Test();
$test->set_reference( array( 1, 1 ) )->add_three()->render(); // should show 203
$test->set_reference( array( 2, 1, 1 ) )->add_three()->render(); // should show 403
I'm struggling with this, especially because there doesn't seem to be a convenient way to access a value inside a nested array given a variable number of indexes. The closest I've gotten is by using eval, but eval seems out of place and doesn't work in all environments which is a nonstarter.
$indexes = "[2][1][1]"; // or "[1][1]" or "[0]"
eval(
"if( isset( $this->data" . $indexes . " ) ) {
$this->current = &$this->data" . $indexes . ";
}"
);
I have also tried doing something with a loop to retrieve the nested value, but I don't know how to change the value $this->current refers to without modifying $this->data.
Use a loop to walk through the index list, keeping a reference as you go.
function set_reference( $indexes ){
$current = &$this->data;
foreach($indexes as $index) {
$current = &$current[$index];
}
$this->current = &$current;
return $this;
}
(If you don't want later modifications to $this->current to affect $this->data, then drop the &s.)
I have this simple function which I pass in an array of strings:
function myfunction( $arg = array() )
{
// do stuff to $arg...
// return a $string;
}
Simple so far, but I need some of the strings in the $arg array to be formatted, while some remain unformatted. I can't figure out how to do it?
Say I run this $arg through myfunction():
echo myfunction( array( 'format me!', 'do not format me!' ) );
My tiny little brain can't figure out how to tell myfunction() that the first value in $arg array needs to have formatting, and it should not format the second value.
I thought about an associative array, but I think that could be the wrong approach because of having identical indexes.
echo myfunction( array( 'format' => 'hi', 'format' => 'bye', 'noformat' => 'foo');
Just looking for a "nudge" in the right direction.
EDIT 1:
Forgot to mention, I can only have one $arg array because I need the keys to be in a specific order.
EDIT 2:
The $arg array can have as many keys as the user wants.
You can do:
function myfunction(array $format, array $noformat) {
...
}
or
function myfunction(array $strings) {
foreach ($strings['format'] as $format) {
// do stuff
}
foreach ($strings['noformat'] as $noformat) {
// do stuff
}
}
with:
myfunction(array(
'format' => array('one', 'two', 'three'),
'noformat' => array('four', 'five', 'six'),
));
If (and only if) the strings are unique you can put them in the key instead of the value:
$strings = array(
'one' => true,
'two' => false,
'three' => false,
'four' => true,
);
myfunction($strings);
with:
function myfunction(array $strings) {
foreach ($strings as $k => $v) {
if ($v) {
// format string
}
}
}
But since you can't have duplicate keys this method falls down if you have repeated strings.
Instead of having myfunction() take an array as an argument, why not just have it take a single element as the argument. Then you can use array_map to process each element of the array.
Sort of like this:
function myfunction($element) {
if( do-formatting) {
//do your formatting stuff
$element = formatted-stuff
}
return $element
}
$arr = array("format me", "don't format me");
//This calls myfunction on each element of the array
//A new array is returned where each element has been replaced
//by the return value from myfunction.
$arr = array_map('myfunction', $arr);
Here is how I would to it.
The implementation of __tostring is not mandatory but is syntax sugar inside the myFunction.
<?php
class MyString{
private $_value;
public $to_format;
public function __construct($str, $to_format = true){
$this->_value = $str;
$this->to_format = $to_format;
}
public function __tostring(){
return $this->_value;
}
}
$args = array( new MyString('format me'), new MyString('Not me!', false) );
This is oddly similar to how table rows are formatted (with a different formatting function for each row). I would provide the data as a single array, then provide formatting information as a second array based on keys. For instance:
function rowFormatter(Array $data, Array $format=array()) {
ob_start();
foreach ($data as $key => $value) {
if (isset($format[$key]))
echo call_user_func($format[$key],$value);
else
echo $value;
}
return ob_get_clean();
}
function makeBold($x) { return '<b>'.$x.'</b>'; }
rowFormatter(array( "Hello", "Mister" ), array( 0 => 'makeBold' ));
I don't know what these strings you are formatting actually represent, so I used generic naming on these classes:
class LineItem
{
var $value;
function __construct($val)
{
$this->value = $val;
}
function __toString()
{
return $this->value;
}
function getFormatted( $formatter )
{
return $this->value;
}
}
class FormattedLineItem extends LineItem
{
function getFormatted( $formatter )
{
return $formatter( $this->value );
}
}
function myfunction( $arr=array() )
{
$fnFormat = function($str) {
return ucwords($str);
};
foreach($arr as $obj)
{
$str = $obj->getFormatted($fnFormat);
echo "$str\n";
}
}
$arr = array(
new FormattedLineItem('format me!'),
new LineItem('do not format me!')
);
myfunction($arr);
The idea is to use separate classes to distinguish between strings.